SpEL Injection in Spring: A Practical AppSec Guide with Examples
SpEL Injection is an important vulnerability for Java, Spring Boot, and Application Security engineers.
SpEL stands for Spring Expression Language. It allows Spring applications to evaluate expressions dynamically.
SpEL itself is not a vulnerability.
The vulnerability occurs when an application takes attacker-controlled input and evaluates it as a SpEL expression.
For an AppSec engineer, the most useful mental model is:
Source → Data Flow → SpEL Sink → Evaluation Context → Impact
1. What Is SpEL?
SpEL (Spring Expression Language) is an expression language provided by the Spring Framework.
It can work with:
Properties
Methods
Objects
Collections
Operators
Spring beans
Java types
A simple example:
ExpressionParser parser =
new SpelExpressionParser();
Expression exp =
parser.parseExpression("2 + 3");
System.out.println(exp.getValue());
Result:
5
SpEL can also access properties and methods:
user.name
user.getName()
This flexibility is useful for developers.
The security problem starts when the expression itself comes from an untrusted user.
2. When Does SpEL Become a Vulnerability?
SpEL Is NOT Automatically Vulnerable
Finding this:
SpelExpressionParser
does not prove a vulnerability.
For example:
ExpressionParser parser =
new SpelExpressionParser();
Expression exp =
parser.parseExpression("2 + 2");
return exp.getValue();
The expression is hardcoded.
Developer
↓
"2 + 2"
↓
SpEL Parser
↓
Evaluation
There is no attacker-controlled input.
Vulnerable Pattern
Now consider:
@GetMapping("/evaluate")
public Object evaluate(HttpServletRequest request) {
String expression =
request.getParameter("expression");
ExpressionParser parser =
new SpelExpressionParser();
Expression exp =
parser.parseExpression(expression);
return exp.getValue();
}
Now the flow is:
Attacker
↓
HTTP Request
↓
request.getParameter("expression")
↓
Attacker-controlled input
↓
parseExpression(expression)
↓
SpEL
↓
getValue()
This is the classic SpEL Injection pattern.
The vulnerability is not the use of SpEL. The vulnerability is allowing untrusted input to become a SpEL expression.
3. Realistic Attack Surface
Suppose the application exposes:
GET /evaluate?expression=10%20%2B%2020
The application executes:
String expression =
request.getParameter("expression");
Expression exp =
parser.parseExpression(expression);
return exp.getValue();
The data flow is:
HTTP Request
↓
expression=10+20
↓
request.getParameter()
↓
"10 + 20"
↓
parseExpression()
↓
SpEL
↓
getValue()
↓
30
The application intended to receive data.
Instead, it has allowed the user to provide executable expression syntax.
That is the fundamental security issue.
4. Source → Sink Analysis
For source-code review, think in terms of source and sink.
Common Sources
Look for attacker-controlled values coming from:
request.getParameter("expression");
request.getHeader("X-Expression");
request.getInputStream();
jsonObject.get("expression");
cookie.getValue();
Other sources may include:
REST API JSON
HTTP headers
Cookies
Message queues
User-controlled configuration
Database values originally supplied by users
SpEL Sinks
Search for:
SpelExpressionParser
ExpressionParser
parseExpression()
Expression
getValue()
StandardEvaluationContext
SimpleEvaluationContext
The security-sensitive flow is:
SOURCE
↓
Attacker-controlled input
↓
Application logic
↓
parseExpression()
↓
getValue()
↓
SpEL Evaluation
↓
Security Impact
This is much more useful than simply searching for SpelExpressionParser.
5. Understanding SpEL Capabilities
Once you establish that attacker-controlled input reaches SpEL, determine what the expression engine allows.
A useful progression for an authorized security test is:
Basic expression
↓
Property access
↓
Method invocation
↓
T() / Java type access
↓
new / object construction
↓
Sensitive Java APIs
↓
Potential RCE
Start with harmless expressions before testing more powerful capabilities.
6. T() — Java Type Access
One of the most important SpEL constructs for AppSec engineers is:
T(...)
For example:
T(java.lang.String)
This references a Java type.
Another harmless example:
T(java.lang.Math).PI
Conceptually:
T(...)
↓
Java Type / Class
↓
Class Members
The security significance becomes much greater when T() can be combined with method invocation.
7. T() → Runtime → exec() — Understanding RCE
A classic SpEL capability chain is:
T(java.lang.Runtime)
↓
getRuntime()
↓
exec(...)
↓
Operating-system command
Conceptually:
T(java.lang.Runtime).getRuntime().exec(...)
In an authorized local lab, a harmless demonstration could be:
T(java.lang.Runtime).getRuntime().exec('touch /tmp/spel-pwned')
The important part is the chain:
SpEL
│
▼
T(java.lang.Runtime)
│
▼
java.lang.Runtime
│
▼
getRuntime()
│
▼
Runtime instance
│
▼
exec(...)
│
▼
Operating-system command
If the application's SpEL configuration permits this capability, the command executes with the privileges of the application process.
This demonstrates why unrestricted SpEL injection can potentially become Remote Code Execution (RCE).
Important
Do not automatically report:
SpEL Injection = RCE
Instead, establish whether the application actually permits:
T()
↓
Runtime
↓
getRuntime()
↓
exec()
8. new — Java Object Construction
Another important SpEL capability is:
new
When permitted by the evaluation context, SpEL can construct Java objects.
For example:
new java.io.File('/tmp/pwn')
This creates a File object.
Method invocation can then be used:
new java.io.File('/tmp/pwn').createNewFile()
In a controlled lab, the security chain is:
SpEL
↓
Java class
↓
Object construction
↓
Method invocation
↓
Filesystem operation
This demonstrates why constructor access + method invocation is important during a security assessment.
9. Network APIs and SpEL
The same concept applies to Java APIs capable of network communication.
For example, in an authorized local environment:
new java.net.URL('http://127.0.0.1:8080/').openStream() != null
Conceptually:
new
↓
java.net.URL
↓
openStream()
↓
Network connection
If an attacker can reach network APIs through SpEL, the impact could extend into SSRF or internal network interaction.
Potential consequences include:
Access to internal services
Internal host probing
Cloud metadata access
Access to services unavailable externally
10. SpEL Injection ≠ Automatically RCE
This distinction is critical.
Finding:
request.getParameter()
↓
parseExpression()
↓
getValue()
establishes a strong SpEL injection condition.
But the actual impact depends on the application's capabilities.
Possible outcomes include:
SpEL Injection
│
├── Property access
├── Method invocation
├── Java type access
├── Object construction
├── File operations
├── Network operations
└── Potential command execution
Therefore:
SpEL Injection is the vulnerability class. RCE is one possible impact.
11. EvaluationContext: The Critical Security Boundary
You may encounter:
StandardEvaluationContext context =
new StandardEvaluationContext();
expression.getValue(context);
The EvaluationContext is extremely important because it influences what the expression can access.
Think of it as the capability boundary around SpEL:
SpEL
│
▼
Evaluation Context
│
┌───────────┼───────────┐
▼ ▼ ▼
Types Methods Beans
│ │ │
└───────────┼───────────┘
▼
Application
During source review, determine:
Can
T()be used?Can
newbe used?Which types are accessible?
Which constructors are accessible?
Which methods can be invoked?
Which Spring beans are exposed?
Which application objects are available?
Are custom resolvers/accessors configured?
These details determine the actual exploitability.
12. SimpleEvaluationContext
Spring also provides:
SimpleEvaluationContext
which is designed for more restricted expression evaluation.
The security principle is:
Dynamic expression required
↓
Use least powerful context
↓
Restrict capabilities
↓
Expose only required functionality
However:
Do not treat
SimpleEvaluationContextas a magic fix for SpEL injection.
The strongest defense remains:
Do not evaluate attacker-controlled expressions.
13. Safe Property DSL: The Better Design
Suppose the business requirement is:
"Users should be able to create dependency-security policy conditions."
You usually do not need to expose the entire SpEL language.
Instead, create a small Domain-Specific Language (DSL) containing only the operations the business actually needs.
Unsafe design
User
↓
Full SpEL
↓
Java objects
↓
Methods / Types / Beans
↓
Potential abuse
Safer design
User
↓
Safe Property DSL
↓
Validate field + operator + value
↓
Allowlisted application logic
↓
Result
For example:
{
"field": "vulnerability.cvssScore",
"operator": "gte",
"value": 9
}
The application might allow only predefined fields:
vulnerability.cvssScore
vulnerability.severity
vulnerability.cve
component.name
component.version
component.license
and operators:
eq
neq
gt
gte
lt
lte
contains
startsWith
in
exists
It should also validate the value type:
Number
String
Boolean
List
Anything outside the grammar is rejected.
Safe DSL Flow
User Input
↓
Safe DSL
↓
Schema Validation
↓
Allowed Field?
↓
Allowed Operator?
↓
Correct Value Type?
↓
Application Logic
There is no need to give the user access to:
T()
new
method calls
beans
Java classes
Why is this safer?
With full SpEL:
expression
↓
SpEL parser
↓
T()
new
method calls
beans
Java classes
With a safe DSL:
field ─────→ allowlist
operator ──→ allowlist
value ─────→ type validation
The user can express business rules, but cannot express arbitrary Java/Spring operations.
AppSec Principle
Don't give users a general-purpose interpreter when they only need a small business-rule language.
This principle applies beyond SpEL to:
SQL
Template engines
Command execution
Scripting engines
Query languages
14. SpEL vs SSTI
SpEL Injection and Server-Side Template Injection (SSTI) are related but different vulnerabilities.
SpEL Injection
Attacker Input
↓
SpEL Parser
↓
Spring Expression Evaluation
SSTI
Attacker Input
↓
Server-Side Template Engine
↓
Template Evaluation
Common template technologies include:
Jinja2
FreeMarker
Velocity
Thymeleaf
The key difference is the interpreter being abused.
SQL Injection
↓
SQL Interpreter
SSTI
↓
Template Interpreter
SpEL Injection
↓
Spring Expression Interpreter
SSTI can also have severe consequences, including sensitive data exposure, server-side actions, and potentially RCE depending on the template engine and configuration.
The common principle is:
Untrusted input must not unexpectedly become executable instructions.
15. SpEL vs SQL Injection
| SQL Injection | SpEL Injection |
|---|---|
| SQL interpreter | SpEL interpreter |
| Database context | Spring/Java context |
| SQL syntax | SpEL syntax |
| Database objects | Java/Spring objects |
| SQL functions | Methods/properties |
| Potential DB compromise | Potential application/JVM compromise |
Mental model:
SQL Injection:
Attacker
↓
SQL
↓
Database
SpEL Injection:
Attacker
↓
SpEL
↓
Spring Expression Engine
↓
Java/Spring Application
The common pattern is:
Untrusted input → Powerful interpreter → Attacker-controlled execution
16. How to Find SpEL Injection During Source Review
Search for:
SpelExpressionParser
ExpressionParser
parseExpression
Expression
getValue
StandardEvaluationContext
SimpleEvaluationContext
Also investigate Spring functionality that uses SpEL:
@Value
@PreAuthorize
@PostAuthorize
@PreFilter
@PostFilter
@Cacheable
@CachePut
@CacheEvict
But don't report a vulnerability simply because one of these appears.
Trace the actual data flow.
For example:
request.getParameter()
↓
Attacker-controlled input
↓
parseExpression()
↓
getValue()
is much more interesting than:
@Value("#{someBean.someProperty}")
where the expression is entirely developer-controlled.
17. SpEL Security Assessment Checklist
When you find a suspected SpEL injection, ask:
1. Can the attacker control the expression?
YES → Continue
NO → Probably not injection
2. Does it reach a SpEL sink?
Look for:
parseExpression(input)
3. Is the expression evaluated?
Look for:
getValue()
or equivalent evaluation.
4. Which EvaluationContext is used?
StandardEvaluationContext
SimpleEvaluationContext
Custom context
5. What capabilities are exposed?
Check:
T()
new
Method invocation
Beans
Properties
Resolvers
Application objects
6. What authentication is required?
Consider:
Unauthenticated
Authenticated user
Administrator
7. What is the actual impact?
Establish whether the attacker can achieve:
Information disclosure
↓
Unauthorized object access
↓
File operations
↓
Network interaction / SSRF
↓
Potential command execution
18. Prevention and Remediation
Preferred Approach
Don't evaluate attacker-controlled SpEL.
Avoid:
String expression =
request.getParameter("expression");
parser.parseExpression(expression);
Instead, expose explicit application functionality or a restricted DSL.
For example:
{
"field": "vulnerability.cvssScore",
"operator": "gte",
"value": 9
}
Then evaluate that condition using predefined application logic.
If Dynamic Expressions Are Absolutely Required
Use defense in depth:
Use the most restrictive evaluation context
Restrict Java types
Restrict constructors
Restrict method invocation
Restrict Spring beans
Avoid exposing sensitive application objects
Validate expression structure
Apply authorization
Monitor and audit expression evaluation
Avoid weak blacklists:
if (input.contains("Runtime")) {
reject();
}
or:
if (input.contains("exec")) {
reject();
}
Blacklists are fragile.
The stronger security boundary is:
Don't give untrusted users a general-purpose expression interpreter.
19. Interview Questions
Q1. What is SpEL Injection?
Answer:
SpEL injection occurs when attacker-controlled input is interpreted as a Spring Expression Language expression. Depending on the evaluation context and accessible capabilities, an attacker may access application objects, invoke methods, perform file/network operations, or potentially achieve RCE.
Q2. Is SpelExpressionParser itself vulnerable?
Answer:
No. SpEL is a legitimate Spring feature. The vulnerability occurs when untrusted input reaches the expression parser/evaluator.
Q3. What is T()?
Answer:
T() allows SpEL to reference a Java type/class. It becomes particularly security-sensitive when an attacker can use it to reach powerful Java APIs.
Q4. How can T() become an RCE primitive?
Answer:
Conceptually:
T(java.lang.Runtime)
↓
getRuntime()
↓
exec(...)
↓
OS command
In an authorized local lab, a harmless demonstration is:
T(java.lang.Runtime).getRuntime().exec('touch /tmp/spel-pwned')
This demonstrates the potential path from SpEL → Java Runtime → OS functionality, assuming the evaluation context permits it.
Q5. What does new mean in SpEL?
Answer:
new can construct Java objects when permitted by the evaluation context. Combined with method invocation, this can expose capabilities such as filesystem or network operations.
Q6. Does SpEL injection always mean RCE?
Answer:
No. RCE depends on the evaluation context, accessible types, constructors, methods, application configuration, authentication, and runtime restrictions.
Q7. What is a safe property DSL?
Answer:
A safe property DSL is a small, purpose-built language exposing only the fields, operators, and value types required by the business. The application validates each component against an allowlist and evaluates the condition using predefined application logic instead of exposing a general-purpose expression engine.
Q8. How do you prevent SpEL injection?
Answer:
The best defense is to never evaluate attacker-controlled SpEL. Prefer a narrowly defined property DSL when business rules are required. If dynamic expressions are unavoidable, use the most restrictive evaluation context possible and restrict types, constructors, methods, beans, and other accessible capabilities.
20. Final AppSec Cheat Sheet
SpEL INJECTION
│
▼
Attacker controls input?
│
▼
request.getParameter()
│
▼
parseExpression()
│
▼
getValue()
│
▼
Evaluation Context
│
┌────────────┼────────────┐
▼ ▼ ▼
T() new Method calls
│ │ │
└────────────┼────────────┘
▼
Java / Spring
capabilities
│
┌────────────┼─────────────┐
▼ ▼ ▼
Data File Network
Access Operations Access
│
▼
Runtime APIs
│
▼
Potential RCE
The 7 Things to Remember
1. SpEL is not inherently vulnerable.
2. request.getParameter() → parseExpression() → getValue() is the classic red flag.
3. T() can reference Java types.
4. T(java.lang.Runtime).getRuntime().exec(...) demonstrates the Java Runtime execution chain in a controlled lab.
5. new can construct Java objects when permitted.
6. The EvaluationContext determines what the expression can actually access and do.
7. Prefer a safe, allowlisted property DSL when users only need to express business rules.
One-line interview answer
SpEL Injection occurs when attacker-controlled input reaches Spring's expression evaluator, allowing the attacker to influence expression execution and potentially access Java/Spring functionality, perform sensitive operations, or achieve RCE depending on the evaluation context and exposed capabilities.
The AppSec Mental Model
Don't memorize payloads first. Learn to trace:
HTTP Request
↓
request.getParameter()
↓
Attacker-Controlled Input
↓
parseExpression()
↓
SpEL Evaluation
↓
EvaluationContext
↓
T() / new / Methods / Beans
↓
Java APIs
↓
Actual Security Impact
And when designing the application, reverse the model:
User
↓
Safe Property DSL
↓
Schema Validation
↓
Allowlisted Fields
↓
Allowlisted Operators
↓
Type Validation
↓
Application Logic
↓
Result
That is the core AppSec strategy: don't expose a general-purpose interpreter when the business only needs a small, controlled language.

