Leveraging Expression Language Injection (EL/OGNL Injection) for RCE

Expression Language injection or EL Injection for short is an attack vector I'd never heard of until recently. This post talks about leveraging EL for RCE.

Leveraging Expression Language Injection (EL/OGNL Injection) for RCE

Expression Language injection or EL Injection for short is an attack vector I'd never heard of until recently. However,  a few days ago I got a message from one of my good friends (Mantis)  asking:

Hey man you ever exploited the JSF-dialect of EL injection?
So using the #{} syntax instead of the ${} one

I hadn't but it got me thinking how to exploit it and got me reading the Java EE docs into how it all worked.  He had found an interesting bug where if you injected some EL syntax into the user agent the application would return varying degrees of info.

We spent ages looking into it trying to better exploit it, before diving into the journey here's a quick explanation of what expression language actually is and how it can be identified.

WTF is an Expression Language(EL)?

You may have heard or seen the notation before in languages like angular JS and other template injection attacks where the common payload is to get the application to evaluate maths such as 9*9 and it will return 81. HAX! Well in this case the application was evaluating Java Server Faces (JSF), here is a quick TL;DR on the lowdown of JSF and EL.

JSF  uses the EL for the following functions when evaluating code:

  • Deferred and immediate evaluation of expressions
  • The ability to set as well as get data
  • The ability to invoke methods

With JSF, EL supports both immediate and deferred evaluation of expressions. Immediate evaluation means that the expression is evaluated and the result returned as soon as the page is first rendered. Deferred evaluation means that the technology using the expression language can use its own libraries and support to evaluate the expression sometime later during the page, whenever it is appropriate to do so.

Those expressions that are evaluated immediately use the ${} syntax which is most commonly found on other expression language injection posts. Expressions whose evaluation is deferred use the #{} syntax which is what this injection post is about.

The injection side of things is essentially server-side code injection, they occur when an application takes user input and passes it to a string that is dynamically executed on the back end usually by some form of language, be it angular, Java(*shivers*)  or something else.  If the user input is not properly sanitized before hitting the back end, an attacker can leverage crafted input to modify the code being run, and inject arbitrary code that will be executed by the server.

The risk of this type of vulnerability is usually pretty high as it can lead to an attacker compromising the entire application and thus gaining access to underlying data and functionality. By leveraging this an attacker may be able to pivot off of the application onto the infrastructure supporting the stack.

Why is this post different from others?

When you Google Expression Language injection, you typically get blog posts explaining how to exploit it when the notation is ${} but hardly ever when it's #{} also referred to as Deferred Evaluation.

This case was deferred evaluation which got Mantis and I thinking on how to exploit, the request was similar to:

GET /login/success?rurl=https%3A%2F%2Fdomain.com HTTP/1.1
User-Agent: #{class}
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Host: blah.domain.com

Notice the user agent is #{class} this is where our injection point was, by supplying content inside the curly braces the application would evaluate the code in the response however it was error based. If the evaluation was successful the code would return a 404 with UserAgent: VALUE where value was the output from whatever was fed in.

We spent ages messing around with this reading the docs and understanding how JSF works with EL. Eventually coming to the conclusion that the following server side variables returned valid data:

  • #{empty}
  • #{class}
  • #{request}
  • #{java}

Moving from this we attempted to get code execution as we had proven that the app was evaluating expressions, it just didn't like quotes which limited our input. Expressions like isEmpty() and #{1==1} both returned true which helped in enumerating information from the back-end.

After further reading we found that Java has a .toString() method which led to more enumeration and getting the app returning different output/evaluating statements then converting them to a string to output, an example of this was:

#{ T(java.lang.Math).random() * 10.0}

Which utilized the Maths function picked a random number and multiplied this by 10.0, at this stage we reported it to the affected program who accepted it, triaged and paid out inside of 12 hours!

All in a good evening, where we both learned a lot and found out about EL, how there's a lack of info out there directly related and led me to writing this short post!