TL;DR
Spring Expression Language (SpEL) is powerful but can be vulnerable to injection if you’re not careful. This guide shows how to avoid letting attackers run arbitrary code on your server by properly sanitising inputs and limiting SpEL access.
Understanding the Risk
Expression Language Injection happens when user-supplied data gets directly used in a SpEL expression without proper validation or escaping. An attacker could craft malicious input that executes unwanted commands, potentially compromising your application’s security.
Prevention Steps
- Avoid Using User Input Directly in SpEL: This is the most important rule. Never directly embed user-provided data into a SpEL expression string.
- Bad Example: Let’s say you have a method that takes a property name from a request parameter:
@RequestMapping("/evaluate") public String evaluate(@RequestParam("property") String propertyName) { String expression = "#{root." + propertyName + "}"; Object result = evaluationContext.evaluate(expression); return result.toString(); }An attacker could set
propertyNameto something likesystemProperties['java.version']or even more dangerous expressions.
- Bad Example: Let’s say you have a method that takes a property name from a request parameter:
- Use Parameterised SpEL Expressions: If you absolutely need to use user input, pass it as a parameter to the expression instead of embedding it directly into the string.
- Good Example:
@RequestMapping("/evaluate") public String evaluate(@RequestParam("property") String propertyName) { String expression = "#{root.properties[?#propertyName]}"; Map<String, Object> params = new HashMap<>(); params.put("propertyName", propertyName); Object result = evaluationContext.evaluate(expression, params); return result.toString(); }This is much safer because SpEL treats
propertyNameas a data value, not executable code.
- Good Example:
- Restrict SpEL Access: Limit which objects and methods are accessible through SpEL.
- Using `@Value` with Restrictions: When using the
@Valueannotation to inject values from properties files, ensure you’re not exposing sensitive operations. - Custom Expression Evaluators: Implement a custom
ExpressionEvaluatorthat only allows access to specific methods and properties. This gives you fine-grained control over what SpEL can do.@Component public class RestrictedExpressionEvaluator implements ExpressionEvaluator { // Your logic to allow/deny access to certain objects and methods }
- Using `@Value` with Restrictions: When using the
- Input Validation: Always validate user input before using it in any part of your application, including SpEL expressions. This includes:
- Whitelisting: Only allow known-good characters or patterns.
- Blacklisting: Disallow potentially dangerous characters or keywords (though this is less reliable than whitelisting).
- Length Restrictions: Limit the length of user input to prevent excessively long expressions.
- Regular Security Audits: Regularly review your code for potential SpEL injection vulnerabilities, especially in areas that handle user-provided data.
Example Scenario and Mitigation
Imagine a system where users can specify a field to retrieve from an object. A naive implementation might look like this:
@RequestMapping("/getfield")
public String getField(@RequestParam("field") String fieldName, @RequestBody MyObject obj) {
String expression = "#{root." + fieldName + "}";
return (String) evaluationContext.evaluate(expression, new Map<String, Object>() {{ put("root", obj); }});
}
An attacker could inject systemProperties['java.version'] as the field name to get system information.
Mitigation: Use parameterised expressions and input validation:
@RequestMapping("/getfield")
public String getField(@RequestParam("field") String fieldName, @RequestBody MyObject obj) {
if (!isValidFieldName(fieldName)) {
throw new IllegalArgumentException("Invalid field name");
}
String expression = "#{root.properties[?#fieldName]}";
Map<String, Object> params = new HashMap<>();
params.put("fieldName", fieldName);
return (String) evaluationContext.evaluate(expression, new Map<String, Object>() {{ put("root", obj); }});
}
The isValidFieldName function would check if the input is a valid field name before allowing it to be used in the expression.

