Get a Pentest and security assessment of your IT network.

Cyber Security

Spring Expression Language Injection: Prevention

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

  1. 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 propertyName to something like systemProperties['java.version'] or even more dangerous expressions.

  2. 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 propertyName as a data value, not executable code.

  3. Restrict SpEL Access: Limit which objects and methods are accessible through SpEL.
    • Using `@Value` with Restrictions: When using the @Value annotation to inject values from properties files, ensure you’re not exposing sensitive operations.
    • Custom Expression Evaluators: Implement a custom ExpressionEvaluator that 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
      }
      
  4. 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.
  5. 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.

Related posts
Cyber Security

Zip Codes & PII: Are They Personal Data?

Cyber Security

Zero-Day Vulnerabilities: User Defence Guide

Cyber Security

Zero Knowledge Voting with Trusted Server

Cyber Security

ZeroNet: 51% Attack Risks & Mitigation