Safeguard
Industry Analysis

Expression Language Injection (ELI) in Java Applications

Expression language injection in Java has powered some of the decade's worst breaches, from Equifax to Confluence. Here's how OGNL and SpEL flaws actually get exploited.

Vikram Iyer
Security Researcher
7 min read

In March 2017, Apache patched a flaw in Struts2's file-upload parser that let attackers slip an OGNL expression into an HTTP Content-Type header and have the server evaluate it as code. Two months later, Equifax was still running the unpatched version. By September, attackers had used that single expression language injection bug to pull records on 147.9 million people — one of the costliest breaches in history. Expression language injection (ELI) isn't a theoretical OWASP category; it's a proven path from a single crafted string to full remote code execution in some of the most widely deployed Java frameworks on earth.

Five years later, the pattern repeated almost exactly: Atlassian Confluence's CVE-2022-26134 let unauthenticated attackers plant an OGNL payload in a URL and take over servers before a patch even existed. This post breaks down what expression language injection actually is, walks through the CVEs that turned it from a code smell into a breach headline, and outlines what teams should be doing about it today.

What Is Expression Language Injection in Java Applications?

Expression language injection happens when an application evaluates user-controlled input as an EL expression instead of treating it as inert data. Java frameworks lean heavily on expression languages — OGNL (Object-Graph Navigation Language) in Struts2, SpEL (Spring Expression Language) in Spring, MVEL in rules engines, and JSP/JSF EL in web tiers — to dynamically bind form fields, route requests, or evaluate configuration at runtime. That dynamism is the point: OGNL can walk an object graph, call methods, and reach static classes like java.lang.Runtime in a single expression.

The vulnerability isn't the expression language itself; it's the trust boundary. When a header, parameter, or filename gets concatenated into an expression string before evaluation, an attacker can append their own OGNL/SpEL syntax and have the interpreter execute it with the application's own privileges. Unlike SQL injection, where the blast radius is a database, EL injection frequently lands directly on the JVM — meaning arbitrary command execution, not just data exfiltration.

How Did OGNL Injection in Apache Struts2 Cause the 2017 Equifax Breach?

It happened because Struts2's Jakarta Multipart parser evaluated attacker-supplied Content-Type headers as OGNL expressions during file uploads — that's CVE-2017-5638, patched on March 6, 2017. The exploit required no authentication: a single crafted HTTP request containing an OGNL payload wrapped in %{...} syntax was enough to trigger command execution on the underlying server.

Equifax had roughly two months to apply the patch before attackers began exploiting an internet-facing dispute-resolution portal in mid-May 2017. The intrusion went undetected until late July, and the company didn't disclose it publicly until September 7, 2017. The financial and reputational fallout — a $700 million settlement, congressional hearings, and a permanent case study in patch-management failure — all traced back to one unsanitized header being fed into OGNL's evaluator. It wasn't even Struts2's only OGNL injection CVE: CVE-2013-2251, CVE-2016-3081, and CVE-2020-17530 (a "forced double OGNL evaluation" bug patched in Struts 2.5.25) show the same root cause resurfacing across seven years of releases.

Why Did CVE-2022-26134 Turn Atlassian Confluence Into a Ransomware Entry Point?

Because it let attackers achieve unauthenticated remote code execution against internet-facing Confluence Server and Data Center instances by injecting OGNL through a crafted URL path — and it was actively exploited as a zero-day before Atlassian shipped a fix. Atlassian disclosed the issue on June 2, 2022, rated it CVSS 9.8, and CISA added it to the Known Exploited Vulnerabilities catalog the same day. Security teams reported webshells appearing on compromised Confluence servers within hours of disclosure, and follow-on campaigns used the foothold to deploy cryptominers and, in several documented cases, ransomware.

What made this one worse than Struts2/Equifax in some respects is the timeline: there was no patch window at all. Confluence is a collaboration hub that typically holds credentials, source snippets, architecture docs, and internal wikis — exactly the kind of high-value target that turns an OGNL injection into a springboard for lateral movement. Confluence had already shipped a near-identical OGNL injection fix a year earlier with CVE-2021-26084, underscoring that expression language injection in a codebase tends to recur unless the underlying pattern — building expressions from untrusted input — is eliminated, not just patched instance-by-instance.

What Makes Spring Expression Language (SpEL) Injection Just as Dangerous?

SpEL injection is just as dangerous because Spring's expression evaluator has the same method-invocation and reflection capabilities as OGNL, and it's wired into far more applications given Spring's dominance in enterprise Java. CVE-2022-22963, disclosed March 29, 2022, let attackers achieve remote code execution in Spring Cloud Function by sending a malicious SpEL expression through the spring.cloud.function.routing-expression HTTP header — no authentication required, CVSS 9.8.

The bug arrived within days of Spring4Shell (CVE-2022-22965), a separate but related class of Spring exploitation, and the overlap caused real confusion in patch triage during that week. SpEL shows up anywhere Spring evaluates @Value annotations, Spring Security access-control expressions (@PreAuthorize), or Thymeleaf templates against attacker-influenced strings. Because SpEL is enabled by default in so many Spring Boot starters, a single misplaced #{} block concatenating request data is enough to reintroduce the same vulnerability class Struts2 users have been fighting since 2013 — just in a different framework, a different syntax, and a codebase that's likely newer and less scrutinized.

How Can Development Teams Detect Expression Language Injection Before Attackers Do?

Teams catch expression language injection before attackers do by treating any dynamic construction of an EL/OGNL/SpEL string from external input as a static-analysis-blocking pattern, not a code-review nicety. Concretely: grep for string concatenation feeding into OgnlUtil.getValue(), ExpressionParser.parseExpression(), MVEL.eval(), or JSP ${}/#{} blocks built from request parameters, headers, or filenames — this is exactly the taint-flow pattern SAST tools should be tuned to flag, and it's the same pattern across all six CVEs discussed above.

Framework-level mitigations matter as much as code fixes: Struts2 has shipped an "OGNL expression injection" allowlist and disabled dynamic method invocation by default since 2.3.20; Spring recommends SimpleEvaluationContext over StandardEvaluationContext whenever an expression doesn't need full method/constructor access, which removes the reflection capability attackers rely on. Dependency and version tracking matters too — Struts2 versions before 2.3.32/2.5.10.1 (Equifax's case), Confluence builds before 7.4.17/7.13.7/7.14.3/7.15.2/7.16.4/7.17.4/7.18.1, and Spring Cloud Function before 3.1.7/3.2.3 all carry these specific EL injection CVEs, so an accurate inventory of what's actually deployed — not just what's in a manifest — is the difference between patching in hours versus finding out from CISA's KEV feed.

How Safeguard Helps

Expression language injection is a supply chain problem as much as a coding problem: Equifax, Confluence, and Spring Cloud Function were all compromised through third-party framework code, not first-party application logic, and all three had known, patched CVEs available before or shortly after mass exploitation began. Safeguard's software supply chain security platform is built for exactly this gap. It continuously inventories the actual Java frameworks, versions, and transitive dependencies running in production — not just what's declared in a pom.xml — so a vulnerable Struts2, Confluence, or Spring Cloud Function build is flagged the moment a CVE like CVE-2017-5638, CVE-2022-26134, or CVE-2022-22963 is disclosed, rather than discovered after exploitation.

Safeguard also correlates vulnerability data with real exploitability and exposure context, prioritizing internet-facing services running affected OGNL/SpEL-capable frameworks over internal, low-risk instances, so security teams aren't triaging hundreds of theoretical findings during a live KEV-catalog event. For teams building custom Java services, Safeguard's pipeline integrations surface risky expression-evaluation patterns and outdated EL-capable dependencies before they reach production, closing the loop between the kind of static-analysis guidance described above and continuous enforcement at build and deploy time. The goal isn't just knowing an EL injection CVE exists somewhere in the SBOM — it's knowing, within minutes of disclosure, exactly which running services are exposed and need to be patched first.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.