Description
Apache Commons JEXL parses strings as expressions or scripts that can later run against context data. JXLT is its template engine. If untrusted text becomes program source, an attacker can choose the operations and control flow the application executes.
The impact depends on the JEXL version, enabled features, permissions, namespaces, exposed context objects and whether the program is actually executed. It can range from changing business decisions and accessing exposed objects to excessive resource use. Broad permissions or legacy configurations can also expose files, networking, reflection, class loading or process execution.
JEXL 3.7.0 changed its default permissions to SECURE and disabled features including new(...), global side effects, pragmas and annotations by default. However, the official documentation states that neither SECURE nor RESTRICTED is a complete sandbox for untrusted input. Script loops remain enabled by default.
Potential impact
- Changes to authentication, pricing, filtering or other decisions that use evaluation results
- Disclosure or modification of data exposed by context objects, namespaces or permitted classes
- Access to sensitive APIs in an engine with broad permissions
- CPU, memory or output exhaustion from long programs, loops, large ranges or costly calculations
Remediation
- Keep expression, script and template source under developer control. Pass request values as
JexlContextdata or declared script arguments, never by concatenating them into source. - If users must select an operation, map a finite set of application-owned keys to reviewed constant programs or Java functions. Prefer a smaller dedicated parser for simple calculations.
- If user-authored JEXL is required, use a supported release and start with
JexlPermissions.NONEorJexlPermissions.create(...), explicitly allowing only needed classes and members. Do not restore legacy defaults or useUNRESTRICTED;SECUREorRESTRICTEDalone is insufficient. - Start with
JexlFeatures.createNone()and enable only required syntax. Minimize context objects, namespaces, imports, arithmetic extensions and class-loader access. A customJexlBuilder.uberspect(...)implementation replaces the builder's permission behavior and needs separate review. - Limit source length and complexity, execution time, CPU, memory and output. Configure
cancellable(true)and interrupt execution at the deadline; use a separate process or container when stronger isolation is required. - Regex or token denylists cannot make arbitrary JEXL source safe. Define a closed set of permitted syntax and object capabilities, and test those authorization decisions.
Examples
Before
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlScript;
import org.apache.commons.jexl3.MapContext;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
class UnsafeJexlController {
@PostMapping("/execute")
Object execute(@RequestParam("script") String script) {
JexlEngine engine = new JexlBuilder().create();
JexlScript compiled = engine.createScript(script); // BAD: parse request text as code
return compiled.execute(new MapContext());
}
}
JEXL 3.7 defaults are stricter than older versions, but the whole request is still interpreted as a program. Scripts may contain loops, and additional permissions or context objects expand what they can do.
After
Keep the source fixed and pass external values as context data.
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.jexl3.MapContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
class SafeJexlController {
private final JexlEngine engine = new JexlBuilder().create();
@GetMapping("/total")
double total(@RequestParam("price") double price,
@RequestParam("quantity") int quantity) {
MapContext context = new MapContext();
context.set("price", price);
context.set("quantity", quantity);
JexlExpression fixed = engine.createExpression("price * quantity");
return ((Number) fixed.evaluate(context)).doubleValue();
}
}
If user-authored syntax is essential, the following excerpt starts from closed permissions and features. It does not replace input limits or resource isolation.
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.introspection.JexlPermissions;
JexlEngine constrained = new JexlBuilder()
.permissions(JexlPermissions.create()) // Deny reflection access by default
.features(JexlFeatures.createNone()) // Explicitly enable only required syntax
.cancellable(true)
.strict(true)
.silent(false)
.create();
References
- Apache Commons JEXL 3.7.0
- JEXL 3.7.0 release notes
- JEXL package guide: configuration and security
- JexlEngine API
- JxltEngine API
- JexlPermissions API and security disclaimer
- JexlFeatures API
- JexlSandbox API
- Legacy JEXL 2.1 JexlEngine API
- CWE-94: Improper Control of Generation of Code
- OWASP Top 10:2025 A05 Injection
- OWASP Top 10:2021 A03 Injection
- OWASP ASVS 5.0