Dan Amodio

Remote Code with Expression Language Injection

Discovering a Spring Framework Vulnerability DanAmodio

This research was cross-posted from Aspect Security.


Update: Jon Passki and Jarek Bojar are keeping me honest. Jarek reported Spring double resolution as SPR-5308 in 2008, which Stefano/Arshan/Myself didn’t find. Too bad it took so long to get fixed. If you want to see my path to exploit, keep reading. Also– check out Jon’s OGNL Injection in Struts. Cheers guys.


In 2011, Stefano Di Paola of Minded Security and Arshan Dabirsiaghi from Aspect Security discovered an interesting pattern in the Spring Framework, which Stefano coined Expression Language (EL) Injection [PDF] [Advisory]. Their discovery revealed that certain Spring tags which double interpret Expression Language can be used to expose sensitive data stored on the server. This is because Spring provides EL support independent of the JSP/Servlet container, as a means for backwards compatibility, since, prior to JSP 2.0, Expression Language wasn’t supported. This functionality is currently turned on by default, and applications that use the patterns described herein are vulnerable.

While it’s difficult to quantify the depth and breadth of this problem since every application will not be vulnerable  as is the case with reflected XSS, we do know, according to recent statistics from Sonatype, that more than 22,000 organizations worldwide have downloaded over 1.314 million individual Spring 3.0.5, or prior. Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads.

These versions do not support disabling the double EL resolution. 

 

The original impact of this issue related to information disclosure, but I’ll illustrate how it can actually be used for remote code execution on Glassfish and potentially other EL 2.2 containers.

Here’s an example of what the original information disclosure attack looked like:

A request of the form:

http://vulnerable.com/foo?message=${applicationScope}

to a page that contains:

<spring:message text="" code="${param['message']}"></spring:message>

will result in output that contains internal server information including the classpath and local working directories.

You can also do other useful things like addition:

${9999+1}

and access session objects and beans:

${employee.lastName}

 

Discovery

While performing a penetration test on a client’s application on Glassfish, I came across this same pattern. Knowing about EL Injection, I did additional testing, confirmed the finding, and moved along; I wanted to unearth the juicy stuff, like XSS.

Alas, the application had an input filter blocking my requests, since they stripped all of the ‘<’ and ‘>’ tags.

On a whim, I thought: “Since I can string manipulate in Java, why don’t I try and do that in EL and bypass the filter?”

So, I attempted the following:

http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPPP

I noticed that the returned error code shows QQQQQ, because the String.replaceAll method has been called, and the returned text is inserted into the spring:message tag.

Here’s the final working vector that bypassed the filter:

http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”<”).replaceAll(“Q”,”>”)}&foo=PscriptQalert(1);P/scriptQ

It worked great, and I thought nothing of it for the next hour or so. Then I realized it was really, really, bad. Why was it possible for me to stick methods in EL like this? That begged the question- what other gnarly things can I do?

After some research, I learned that the EL 2.2 added support for method invocation.

 

Taking it Further

I wrote a quick test application and started checking out some functionality:

${pageContext.request.getSession().setAttribute("account","123456")}
${pageContext.request.getSession().setAttribute("admin",true)}

OK, session object modification is a definite risk. I really wanted to touch objects I didn’t have a direct pointer to through the pageContext. Maybe we can use reflection, like String.getClass().forName(string)?

${"".getClass().forName("java.net.Socket").newInstance().connect("127.0.0.1", 1234)}
${"".getClass().forName("java.lang.Runtime")}

Wow, there’s no way that should work! This could be disastrous because you can touch just about anything. Unfortunately, it’s not possible to call newInstance() for numerous dangerous classes (like Runtime), as they do not provide default constructors. We were unable to cast objects, and there are some issues with getMethods()[0].invoke() when it requires null or a null array. EL seems to resolve these as a string literal before passing the data to the method. I assume this is due to the method signature invoke(O