Expression Language Injection in Java Spring Applications
Unsafe evaluation of user input in Spring Expression Language can lead to remote code execution.

Expression language injection in Java Spring applications begins when user data becomes executable expression text. SpEL’s flexibility makes that mistake dangerous because its evaluator can reach far beyond a value in a template. Closing the attack surface means keeping input out of expression strings, restricting the evaluation context, and testing every place where the two can meet.
What SpEL is and why its power is the source of its risk
Spring Expression Language is a small language that application code can evaluate at runtime. It supports property access, arithmetic, method calls, bean references, type resolution through the T() operator, and object creation. Expressions inside #{...} delimiters can therefore do more than substitute a name or display a value. They can select objects and invoke behavior within the application. That reach is the architectural difference between SpEL and simple text substitution.
SpEL also appears in several parts of a Spring application, rather than in one obvious expression-processing feature. An @Value("#{...}") annotation can inject a dynamic value, while Spring Security uses expressions in annotations such as @PreAuthorize. Spring Cloud Gateway uses SpEL in route predicates and filters, and Thymeleaf evaluates ${...} variable expressions through SpEL in features such as th:text. Spring Data @Query annotations can also accept restricted SpEL fragments. These uses make expression evaluation part of configuration, access control, rendering, and data access. Its security therefore depends on more than the template layer.
How untrusted input reaches the evaluator — the injection mechanism
The injection boundary is crossed when an application treats user-supplied text as part of an expression. A common pattern is to concatenate input directly into an expression string and pass that string to the parser. Without an allowlist for the expression components, the parser has no way to distinguish the developer’s intended logic from attacker-supplied syntax. The same opening can arise when a filter expression key is added without proper validation or escaping. The input no longer just supplies data to the operation, but changes the operation itself.
OWASP’s EL Injection reference shows a less obvious way in through Spring JSP tags that resolve expression language twice. A controller first redirects an error message into a URL parameter using redirect:/error?msg=error.generic. The JSP then renders the parameter with <spring:message code="${param.msg}" />. At first glance, the tag appears to be using the parameter only as a message code. Double-resolution changes that assumption because the value obtained from the parameter can itself be evaluated as an expression.
The request ?msg=${param.test}&test=INJECTION exposes the second evaluation. The tag evaluates ${param.test} and receives the string "INJECTION" as its message code. The developer did not need to write that expression or add a feature for changing the session. The tag’s handling of externally supplied text supplied the missing link between a URL parameter and application behavior.
From expression evaluation to remote code execution
SpEL supports type resolution through T() and method calls, which can provide a bridge to Java runtime capabilities. Process execution is the severe end of that bridge because it lets an expression invoke the JVM’s process-execution interface. OWASP’s EL Injection reference gives ?vulnerable=T.getRuntime().exec("cmd.exe") as an example passed to a <spring:eval> tag that would provide system code access.
A command may have run even if its result is missing. In blind testing, where the application swallows output, invoking Thread.sleep(10000) can produce a ten-second delay that signals execution of a user-specified Java function. Another signal comes when exec() returns a ProcessImpl object and a later concatenation fails. The resulting error is EL1030E: The operator 'ADD' is not supported between objects of type 'null' and 'java.lang.ProcessImpl'. In that sequence, the error follows the process being spawned, so a failed expression result is not the same as a blocked command.
OWASP’s EL Injection reference describes consequences that include reading sensitive server-side information and invoking or modifying application behavior. The information exposed can include model objects, beans, session scope, and application scope. A successful injection can also give an attacker unauthorized account-level access, allow remote code execution, and put the server at risk of compromise. The evaluation context determines how far that chain can go. The CVE record shows that this is a production vulnerability class that demonstrates in practice what an expression language could theoretically do.
The CVE record — how SpEL injection has surfaced across the Spring ecosystem
CVE-2011-2730 concerned double-resolution in Spring Framework JSP tags. OWASP’s EL Injection reference shows how URL parameters could cause <spring:message> and <spring:eval> to evaluate arbitrary expressions. The vulnerable behavior sat between the application’s apparent use of a value and the framework’s interpretation of it.
In CVE-2022-22947, the way in was through Spring Cloud Gateway. An actuator endpoint allowed a malicious route containing a SpEL filter expression to be added, yielding remote code execution on the gateway server. CVE-2025-41243 also involved Spring Cloud Gateway Server, but through unsecured Spring Boot actuator endpoints that allowed Spring Environment property modification enabling code execution. The official Spring advisory identifies Webflux applications as affected and WebMVC applications as not vulnerable. Snyk’s vulnerability record gives the fix as upgrading org.springframework.cloud:spring-cloud-gateway-server to version 4.2.5 or 4.3.1.
CVE-2026-41719 shows the same class of problem in Spring Data KeyValue. NVD describes unsanitized user input passed as a Sort parameter into a repository query method that delegates evaluation to SpelPropertyComparator. The vulnerability is rated medium severity at CVSS 6.4. These incidents span more than a decade, with entry points in JSP tags, gateway configuration, and repository sorting.
The evaluation context decision that determines exploitability
The most consequential local decision is the context in which an expression runs. StandardEvaluationContext exposes the full SpEL feature set, including method invocation, type resolution through T(), reflection-based access, and bean references. It is intended for internal framework logic operating on fully trusted data. When attacker-controlled expression text reaches that scope, the evaluator can let it do far more than the application intended. A parser call that looks routine can therefore sit on a path to arbitrary code execution.
SimpleEvaluationContext has a restricted evaluation model that allows property access and a limited set of operations. Its security value is that user-facing filtering need not receive the full capabilities of the standard context. The restricted model excludes the type access used in the RCE path, and method invocation must remain outside the permitted operations for that protection to hold. The distinction is between evaluating a limited operation over data and giving an expression broad access to application behavior. Those are different jobs, even when both use SpEL.
That distinction makes the choice of context something to audit, beyond a choice of coding style. An expression’s apparent purpose, such as selecting a property or applying a filter, does not by itself restrict what the evaluator can execute. Developers may choose StandardEvaluationContext out of habit or follow an example without deciding which data to trust. Remediation has to narrow both the expression’s inputs and the evaluator’s reach.
Concrete remediation steps for SpEL injection
The primary defense is to remove user input from expression strings entirely. APIs and templates should pass user-controlled values as variables bound to a pre-built expression, rather than concatenate those values into expression text. This preserves the separation between the operation and its data. It also addresses the failure before a parser has to interpret attacker-controlled syntax. Input filtering alone does not provide that architectural boundary.
The next layer is to replace StandardEvaluationContext with SimpleEvaluationContext wherever expressions touch user-supplied data. The restricted context should permit only the property access and limited operations the feature needs, without the type resolution and method invocation that enable the RCE path. Used together, fixed expression text and restricted evaluation do most of the defensive work. Neither should be treated as a reason to leave the other boundary loose.
Legacy Spring JSP applications have a more specific control. OWASP’s EL Injection reference recommends disabling double-resolution in Spring 3.0.6 and above by adding <context-param><param-name>springJspExpressionSupport</param-name><param-value>false</param-value></context-param> to web.xml. In versions before 3.0.6, that behavior cannot be disabled, so the vulnerable pattern must be avoided. Where an expression component cannot be fully isolated from input, an allowlist should define the permitted field names, operators, or values. A denylist of known-bad characters is not a substitute for defining what the feature actually accepts.
Dependencies should be kept up to date, including affected versions of Spring Cloud Gateway Server, Spring AI, and Spring Data KeyValue. An application-level control is not a replacement for fixing a vulnerable component version. For the Spring Data KeyValue pattern in CVE-2026-41719, user-controlled values should not be passed as Sort parameters into repository queries that use SpelPropertyComparator. These narrower controls address the specific framework paths left beyond the first two defenses.
How to test for SpEL injection — from black-box probing to whitebox source review
Black-box testing probes user-controllable fields, URL parameters, headers, and body values for expression behavior. Candidate probes include ${{7*7}} for arithmetic evaluation and ${"test".length()} for a result of 4 [49]. A Java version string or a SpEL evaluation error in a response can provide further evidence that input reached the evaluator.
The subsystem behind that response matters. Server-side template injection and EL injection can share the ${7*7} probe and similar RCE payload structures, but they target different layers. SSTI targets a template-rendering engine, while EL injection targets an expression evaluator that is part of a Java framework. A result of 49 in a Spring stack points toward EL injection, but arithmetic alone does not explain the entire path. The follow-up task is to locate the evaluator and determine what context governs it.
When the response hides expression output, testing needs another observable signal. The delays and errors after execution described earlier can help test for blind execution. DNS or HTTP out-of-band callbacks can also provide confirmation when the application surface is fully opaque. These methods make the effects of the same injection mechanism observable. Silence in the response leaves it unclear whether execution occurred.
Whitebox source review replaces that inference with direct inspection of evaluation sites. Searching for new SpelExpressionParser() and StandardEvaluationContext instantiation locates the code where expressions are parsed and given their capabilities. Review can then follow input into those sites, including deeply embedded paths and logic errors that black-box probing may not reach. In a Spring Boot penetration test, the scope also includes actuator endpoints, user-exposed @Value expressions, and Spring Data Repository @Query annotations containing SpEL. Source review makes coverage of those paths deterministic rather than dependent on discovering the right response anomaly, and verifies the essential boundary that user data stays data.


