Expression Language (EL) injection

Expression Language (EL) injection

Description

Java/Jakarta Expression Language (EL) can read or modify object properties and invoke methods through expressions written as strings. Using untrusted input as expression source erases the boundary between data and executable structure, creating EL injection.

Jakarta Expression Language 6.0 imports java.lang.* by default and supports references to public static fields, methods, and constructors. The actual impact depends on the beans, functions, resolvers, and imports in the evaluation context. An attacker may read or modify accessible application data and invoke methods; exposed capabilities may also permit operating-system command execution.

The main APIs behave as follows:

API Expression argument Behavior
ELProcessor.eval(expression) First Evaluates the expression immediately.
ELProcessor.getValue(expression, type) First Evaluates the expression and converts its result to the specified type.
ELProcessor.setValue(expression, value) First Evaluates the target expression and changes the final property or variable. The second argument is data.
ELProcessor.setVariable(name, expression) Second Parses an expression for later use and associates it with a variable. The first argument is the variable name.
ExpressionFactory.createValueExpression(context, expression, type) Second Parses a value expression for later evaluation.
ExpressionFactory.createMethodExpression(context, expression, ...) Second Parses a method expression for later invocation.

javax.el is the older Java EE namespace; jakarta.el is the Jakarta EE namespace.

Potential impact

  • Unauthorized reads or changes to application data exposed in the evaluation context.
  • Calls to accessible objects, static methods, or constructors and misuse of application functionality.
  • Operations with server privileges if file, network, or operating-system command APIs are reachable.
  • Resource consumption through expensive expressions or stream operations.

Remediation

  1. Keep EL expression text as fixed, developer-managed strings. Do not concatenate request values or external configuration into expressions or parse and evaluate them directly.
  2. Bind untrusted values as data, not expression source. Use ELProcessor.defineBean or VariableMapper, then evaluate only fixed expressions referring to the bound values.
  3. If users must select an operation, map a finite set of server-managed keys to reviewed constant expressions. A regular expression accepting arbitrary text or a generic escaping function does not secure the full EL grammar.
  4. Expose only the beans, functions, resolvers, and imports required by fixed expressions, and run with least privilege. This is defense in depth, not a sanitizer for attacker-controlled expressions.
  5. Remove or disable evaluation where EL is unnecessary. Jakarta EL 6.0 removed references to Java SecurityManager; do not rely on it as a standard sandbox defense.

Examples

Before

java
import jakarta.el.ELProcessor;
import jakarta.servlet.http.HttpServletRequest;

final class UnsafeElEvaluation {
    Object evaluate(HttpServletRequest request) {
        String expression = request.getParameter("expression");

        ELProcessor processor = new ELProcessor();
        return processor.eval(expression); // Evaluate the request value as EL source
    }
}

The request value is passed directly as the expression argument of ELProcessor.eval. An attacker can inject EL syntax selecting properties or methods accessible in that context.

After

java
import jakarta.el.ELProcessor;
import jakarta.servlet.http.HttpServletRequest;

final class SafeElEvaluation {
    Object display(HttpServletRequest request) {
        String displayName = request.getParameter("displayName");

        ELProcessor processor = new ELProcessor();
        processor.defineBean("displayName", displayName); // Bind the request value as data
        return processor.eval("displayName");             // The developer fixes the expression
    }
}

The request value is registered only as bean data, while the evaluated expression is fixed. Even when users select between functions, explicitly map each selection key to a server-managed constant expression.

Usage considerations

ExpressionFactory.create*Expression and ELProcessor.setVariable parse expressions for later use. Follow the returned expression or variable to its actual evaluation. Pass input as data to fixed expressions, and do not evaluate bound values again as expression source.

References