服务端模板注入(SSTI)漏洞进阶与防护(实战篇)
字数 1590 2025-11-26 20:16:58
服务端模板注入(SSTI)漏洞进阶与防护(实战篇)
1. 漏洞描述
服务端模板注入(SSTI)是攻击者通过操纵模板引擎的输入,注入恶意代码并使其在服务器端执行的漏洞。与普通注入不同,SSTI利用的是模板引擎的语法特性,允许直接执行服务器命令或访问敏感数据。常见模板引擎包括Jinja2(Python)、Twig(PHP)、Freemarker(Java)、Handlebars(JS)等。实战中,SSTI可能出现在动态生成邮件内容、报告页面或用户自定义模板等功能中。
2. 漏洞原理与危害
- 原理:
模板引擎通常允许嵌入变量或表达式(如{{user_input}}),若用户输入未经严格过滤直接拼接进模板,攻击者可插入模板语法(如{{7*7}}),若页面返回49则存在漏洞。进一步可调用危险函数(如Python的os.system、PHP的system)。 - 危害:
- 远程代码执行(RCE),控制服务器。
- 敏感文件读取(如
/etc/passwd)。 - 服务中断或数据泄露。
3. 漏洞检测步骤
步骤1:识别模板引擎类型
向输入框提交测试载荷,观察响应:
- Jinja2:
{{7*7}}→ 返回49。 - Twig:
{{7*7}}→ 返回49。 - Freemarker:
${7*7}→ 返回49。 - 若表达式被原样输出,可能需尝试其他语法或上下文。
步骤2:确认注入点
测试常见边界场景:
- URL参数:
http://site.com/page?name={{2*2}} - 表单字段:用户注册时的"用户名"字段。
- 文件上传:文件名被用于模板生成。
步骤3:验证命令执行能力
通过内置函数探测环境:
- Jinja2:
{{config.items()}}(泄露Flask配置)。 - Twig:
{{_self.env.registerUndefinedFilterCallback("exec")}}(调用系统命令)。
4. 漏洞利用实战(以Jinja2为例)
场景:网站用户主页通过http://site.com/user/{{username}}动态生成,用户名未过滤。
- 探测:输入用户名
{{7*7}},若页面显示"Hello 49",确认漏洞。 - 读取文件:
解释:通过Python对象继承链找到{{ ''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read() }}file类,调用read()方法。 - 执行命令:
利用Flask配置对象的全局模块{{ config.__class__.__init__.__globals__['os'].popen('id').read() }}os执行系统命令。
5. 进阶绕过技巧
- 字符串拼接绕过黑名单:
若过滤了os,可改用{{request.application.__globals__.__builtins__.__import__("o"+"s")}}。 - 十六进制/Unicode编码:
{{eval("\x5f\x5fimport\x5f\x5f('os').system('ls')")}}。 - 利用模板内置变量:
Twig中{{_self}}、Jinja2中{{request}}可能包含危险方法。
6. 防护措施
- 输入验证:
严格限制用户输入格式(如用户名仅允许字母数字),拒绝包含模板语法的内容。 - 沙箱环境:
禁用模板引擎的危险函数(如Jinja2的{% debug %}),使用沙箱模式(如Jinja2的SandboxedEnvironment)。 - 静态模板:
避免将用户输入直接嵌入模板,改用预定义变量(如render_template("page.html", user=input_sanitized))。 - 最小权限原则:
模板引擎进程以低权限用户运行,限制文件系统访问。 - 日志监控:
记录模板渲染错误,检测异常载荷(如{{频繁出现)。
7. 实战防护示例(Flask + Jinja2)
from jinja2 import Template, BaseLoader, Environment
# 错误示例:直接渲染用户输入
template = Template("Hello " + user_input) # 危险!
# 正确示例:静态模板 + 转义
template = Template("Hello {{ name }}")
output = template.render(name=escape(user_input)) # 使用HTML转义
# 或使用沙箱环境
env = Environment(extensions=['jinja2.sandbox.SandboxedEnvironment'])