Reminiscences of Another EL Injection

Evaluating an input is a requirement for most of the custom web, mobile or desktop applications let alone popular frameworks. In this post, focusing on Spring Expression Language (SpEL) we’ll try to explain the concept of least privilege as a mechanism to prevent any unlawful behavior through expression language evaluation mechanisms.

Ever forgotten but persistently resurrected EL Injection problems can be alleviated by using the least privilege concept.

Recently, on 26 March 2022, a proof of concept has been published that exploits a vulnerability on Spring Cloud Function (SCF) project. The vulnerability can be tracked with CVE-2022–22963. Here’s a short introduction to the SCF and the root cause of the problem.

An Introduction and Root Cause for CVE-2022–22963

As part of Spring Cloud project, which aims to provide tools for developers to build in distributed systems, Spring Cloud Function (SCF) mainly tries to achieve the implementation of business logic via functions. It has some cool features such as having a REST support to expose functions as HTTP endpoints, moreover, it promotes less code and less configuration.

Obviously, routing plays an important role to achieve these goals in an distributed environment. And the routing is implicitly enabled by setting the property, which can also be set via an HTTP header. In more detail, the routing instruction that comes from the value of this property should result in the definition of the function to which to route. And here, the critical part is that the value provided is designed to be expressed in Spring Expression Language (SpEL for short).

So, in a nutshell, an HTTP header is accepted as an SpEL which naturally will be evaluated eventually somewhere; An untrusted input is being evaluated in a trusted environment. Hmm… This support has been around in SCF from 2019. More hmmm.

In general, expression languages have a popular history of being abused by malicious users. The Common Weakness Enumeration project has a separate weakness for this, namely CWE-917. And Spring Expression Language is no different.

Getting input from untrusted users and running them is a popular requirement for most of the custom web, mobile and desktop applications. For example, from time to time as a developer, we have to accept rich text from our application users and render this text again in the browsers as HTML/CSS even JavaScript . This leads to possible client side injections, which are perhaps less riskier than server side injections, but basically the same concept.

The Spring Expression Language

Since the vulnerability we will see is about Spring Expression Language, let’s take a look at it shortly. SpEL is an expression language supporting querying and manipulating an object graph at runtime. It has method invocation, bean references, properties and string templating features, as well as, ability to execute user defined functions. These features make it a powerful language yet dangerous.

Below are some of the examples of SpEL expressions each on a separate line;

'Hello' + ' World'
1 ge 1
5 > 1 and 4 < 6
myBean.calculate(5) > 4
new String('Hello') == 'Hello'

The last three show that property access, method invocation and object creation are also possible.

SpEL is produced as part of the Spring project, however, it can also be used independently by importing some infrastructure classes, one of the most important one is parser.

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'World'");
String message = (String) exp.getValue();

The code above, parses the string expression which is in bold and retrieves the value by evaluating it. It’s also possible to call methods, access properties or call constructors. So,

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'World'.concat('.')");
String message = (String) exp.getValue();

The above code shows SpEL supports method call on a String instance by default.

Evaluation Contexts

When evaluating an expression, in order to resolve properties, methods, fields and perform type conversion EvaluationContext is used as an interface. And the default implementation of this interface is called StandardEvaluationContext, which extensively uses reflection to achieve these goals.

When no evaluation context is provided to getValue method of ExpressionParser, a StandardEvaluationContext is being used. In more general terms, by default, the SpEL evaluation is really really powerful. Dangerously powerful.

How dangerous? Well, the code below runs a calculator on the runtime environment it executes (in my case a Windows OS);

String input = "T(Runtime).getRuntime().exec('calc.exe')";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(input);

That means it can execute any OS command as long as the user running the encompassing process has right to call.

As a side note, in the above input expression it’s possible to access static methods with a T operator and we don’t have to fully qualify the Runtime, since for the type locator used by the StandardEvaluationContext, the references to types within java.lang do not need to be fully qualified.

Principle of Least Privilege to the Rescue

Unsurprisingly, we can see that the vulnerable routing part from the HTTP header of SCF project uses SpelExpressionParser’s getValue with a StandardEvaluationContext.

The deleted code (highlighted in red) utilizes the dangerously powerful StandardEvaluationContext.

And the created line at 209 (highlighted in green), shows that when the expression comes from an untrusted source, such as an HTTP header, a so called SimpleEvaluationContextis being used. How this evaluation context is different than the standard one?

Enter SimpleEvaluationContext

Directly from the Spring Java Doc,

[SimpleEvaluationContext is] a basic implementation of EvaluationContext that focuses on a subset of essential SpEL features and customization options, targeting simple condition evaluation and in particular data binding scenarios.

In many cases, the full extent of the SpEL language is not required and should be meaningfully restricted. Examples include but are not limited to data binding expressions, property-based filters, and others. To that effect, SimpleEvaluationContext is tailored to support only a subset of the SpEL language syntax, e.g. excluding references to Java types, constructors, and bean references.

I tried to show the important part in bold. It seems the SimpleEvaluationContext enforces a strict limitation for any expression that is evaluated under it. Let’s see what the original calculator executing code results in when used with a SimpleEvaluationContext.

String input = "T(Runtime).getRuntime().exec('calc.exe')";EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(input);

The code above, when run, throws the following exception;

Exception in thread "main" org.springframework.expression.spel.SpelEvaluationException: EL1005E: Type cannot be found 'Runtime'
at ...

That means, aligned with the JavaDoc above, we can’t access Java types and can’t call methods when SimpleEvaluationContextis used. This is obviously a more secure way of sandboxing the SpEL evaluations. Albeit it’s not promoted enough, the JavaDoc has it and developers who are dealing with SpEL should really know about sandboxing their SpEL executions.

From a static code analysis perspective, at the very least, SpEL with explicit or implicit usages of StandardEvaluationContextshould be located, triaged and mitigated.

// implictly using a StandardEvaluationContext
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(inputSpEL);
String message = (String) exp.getValue();
// explicitly using a StandardEvaluationContext
EvaluationContext context = new StandardEvaluationContext();
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(inputSpEL);
String message = (String) exp.getValue(context);

Needless to say, there are also overloaded versions of the getValue and other methods that are used to evaluate SpEL under the Expressionclass, such as setValue, that should be taken care of.

The Aftermath

So, the vulnerability CVE-2022–22963 is fixed by applying a least privilege concept that runs the untrusted SpEL inside a sandbox.

And surely this hardening on SCF v3.1.7/v3.2.3 and onwards would drastically diminish the flexibility and power of using SpEL for routing, right?

For example, SCF won’t be able to use SpEL below as the values of an HTTP header. Since there is a function call (toString()) inside of it.

headers.contentType.toString().equals('text/plain') ? 'echo' : 'devNull'

When run, it will probably throw an exception saying;

...SpelEvaluationException: EL1004E: Method call: Method toString() cannot be found on type

But it seems and correctly loosing expressiveness is sometimes preferable than loosing trust, money or popularity :)

As a last word, even inside a sandbox can’t a malicious user still abuse function routing via HTTP headers by providing let’s say complex regular expression patterns and do expensive pattern matching? That is can’t we pull a regular expression denial of service attack (ReDOS) here by providing below SpEL expression as the header value?

'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!' matches '^([a-z]+ ?)+$'

Well, it seems the Spring Core team has already fixed this bug in 2018 :) You can follow this Spring Github issue for details. I tried it with a JDK 1.8 and have to say I really like the way they extend CharSequence right in the Pattern matching without any extra thread/time controlling execution as they did in .NET.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store