icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons icons_061

[R2] Pivotal Spring Framework HttpInvokerServiceExporter readRemoteInvocation Method Untrusted Java Deserialization



Current installations of Pivotal's Spring Framework suffer from a potential remote code execution (RCE) issue. Depending on how the library is implemented within a product, it may or may not manifest, and authentication may be required. We have confirmed that current integration in commercial vendor products are affected, so this is not academic. The following write-up is based on how one vendor implemented the Spring Framework and became vulnerable, but illustrates how many other products and vendors could be impacted as well.

What Is HttpInvokerServiceExporter?

The Spring Framework Javadoc describes HttpInvokerServiceExporter as a “Servlet-API-based HTTP request handler that exports the specified service bean as HTTP invoker service endpoint, accessible via an HTTP invoker proxy.” To the layperson, this essentially means that a client can execute specific methods exposed by the creator of the server application. This functionality is very similar to RMI. In fact, the JavaDoc further describes HttpInvokerServiceExporter in terms of RMI: “Deserializes remote invocation objects and serializes remote invocation result objects. Uses Java serialization just like RMI, but provides the same ease of setup as Caucho's HTTP-based Hessian and Burlap protocols.

This Feels Oddly Familiar…

Good, because it should. In 2011, Wouter Coekaerts achieved remote code execution by deserializing proxies through this endpoint. This was assigned CVE-2011-2894 and was fixed by Pivotal by adding a flag to RemoteInvocationSerializingExporter indicating if proxy classes can be deserialized and restricting how DefaultListableBeanFactory could be deserialized. Wouter also did a very nice write-up on this vulnerability and, in 2013, Alvaro Muñoz published a working exploit.

Still Deserializing All The Things

During recent plugin development, it led Tenable to dig around a commercial product that integrates the Spring Framework. It was found to have an HTTP interface that used HTTPInvokerServiceExporter. Not knowing much about this class, we did what any good researcher would do; throw a GET request to the interface like a champ. Oddly enough, it produced a spinner and then an error message. Checking the server log:

at java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2328)
at java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:2797)
at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:802)
at java.io.ObjectInputStream.(ObjectInputStream.java:299)
at org.springframework.core.ConfigurableObjectInputStream.(ConfigurableObjectInputStream.java:64)
at org.springframework.remoting.rmi.CodebaseAwareObjectInputStream.(CodebaseAwareObjectInputStream.java:97)
at org.springframework.remoting.rmi.RemoteInvocationSerializingExporter.createObjectInputStream(RemoteInvocationSerializingExporter.java:123)
at org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter.readRemoteInvocation(HttpInvokerServiceExporter.java:115)
at org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter.readRemoteInvocation(HttpInvokerServiceExporter.java:96)
at org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter.handleRequest(HttpInvokerServiceExporter.java:73)
at org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter.handle(HttpRequestHandlerAdapter.java:51)

For those that don’t stare at Java cruft for a good part of their day, this call stack tells us that HttpInvokerServiceExporter is trying to create an ObjectInputStream. Now, the application being examined is using Spring 4.1.4 but we’ll quote directly from 'master' on GitHub (8213df817e1a0f595e6aa55fecb7a5d5777f8236) for easier copy/paste access and because the code hasn’t really changed. The following method is HttpInvokerServiceExporter’s handleRequest:

public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
{ RemoteInvocation invocation = readRemoteInvocation(request); RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy()); writeRemoteInvocationResult(request, response, result); }
catch (ClassNotFoundException ex)
{ throw new NestedServletException("Class not found during deserialization", ex); }

This function is the entry point into HttpInvokerServiceExport. From the stacktrace, we know that we need to follow the HTTP request down into the readRemoteInvocation method. Which looks like this:

protected RemoteInvocation readRemoteInvocation(HttpServletRequest request)
throws IOException, ClassNotFoundException
{ return readRemoteInvocation(request, request.getInputStream()); }
Next, we have to follow the HTTP request and its payload (that is what getInputStream() is exposing) to readRemoteInvocation():
protected RemoteInvocation readRemoteInvocation(HttpServletRequest request, InputStream is)
throws IOException, ClassNotFoundException {
ObjectInputStream ois = createObjectInputStream(decorateInputStream(request, is));
{ return doReadRemoteInvocation(ois); }
{ ois.close(); }

Notice that the HTTP request’s payload just got converted into an ObjectInputStream and passed to doReadRemoteInvocation. The $1,000 question Alex, is what does doReadRemoteInvocation do with the ObjectInputStream? Looking at more code:

protected RemoteInvocation doReadRemoteInvocation(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
Object obj = ois.readObject();
if (!(obj instanceof RemoteInvocation))
{ throw new RemoteException("Deserialized object needs to be assignable to type [" + RemoteInvocation.class.getName() + "]: " + obj); }
return (RemoteInvocation) obj;

And we find classic untrusted deserialization. But there is hope! Perhaps their ObjectInputStream uses IBM’s look ahead method. Peeking into the createObjectInputStream() function we saw in readRemotInvocation we discover that the type of ObjectInputStream created is Spring Framework’s CodeAwareObjectInputStream: https://github.com/spring-projects/spring-framework/blob/183594207fbb447e1b59262b4469f2aefbb8a3ec/spring-context/src/main/java/org/springframework/remoting/rmi/CodebaseAwareObjectInputStream.java

Does it implement the look ahead technique? Unfortunately, no it does not. #SadPanda

Is that significant? Yes, as long as the appropriate libraries are included in the product we can exploit this endpoint using an HTTP POST request with a ysoserial gadget (or other unpublished gadgets) in the payload.

About Authentication

In the product we were examining, the above leads to unauthenticated remote code execution. While Spring does offer Spring Security which would require authentication before reaching this endpoint (as noted on Stack Overflow), it does not protect an application for authenticated RCE. It also won’t protect those who chose not to use Spring Security as the product being examined did. But, that is for another advisory.

Furthermore, we couldn't find any type of warning in the Javadoc or elsewhere about the possible dangers of exposing HttpInvokerServiceExporter to client requests. Based on a lack of documentation and warning, we feel that this arbitrary deserialization of all objects it not a feature, but an oversight.


Pivotal replied to our report, saying:

A look-ahead check initially sounds worthwhile for this particular case where we only intend to deserialize an instance of the RemoteInvocation class... However, RemoteInvocation may contain any argument values in its nested arguments array, so we wouldn't be gaining anything in practice.
Our general advice applies: Do not use Java serialization for external endpoints, in particular not for unauthorized ones. HTTP invoker is not a well-kept secret (or an "oversight") but rather the typical case of how a Spring application would expose serialization endpoints to begin with... he has a point that we should make this case all across our documentation, including the javadoc. But I don't really see a CVE case here, just a documentation improvement.

Pivoltal will enhance their documentation for the 4.2.6 and 3.2.17 releases.

Disclosure Timeline

2016-04-06 - Issue discovered
2016-04-08 - Submitted to ZDI for consideration, case bainesjr0006 opened
2016-04-11 - ZDI declines offer #SadEmoji
2016-04-13 - Tenable contacts customer-service@pivotal.io for vuln reporting procedure
2016-04-26 - Tenable contacts customer-service@pivotal.io again
2016-04-26 - Automated reply, #26222 opened
2016-04-26 - Pivotal replies, looking for best contact, will get back to us
2016-04-26 - Pivotal replies, use security@pivotal.io for vuln reporting
2016-04-26 - Tenable sends details to security@pivotal.io
2016-04-27 - Pivotal acks mail, passed it to Spring team
2016-05-04 - Confirms issue, working as intended, will enhance documentation for 4.2.6 / 3.2.17 releases

All information within TRA advisories is provided “as is”, without warranty of any kind, including the implied warranties of merchantability and fitness for a particular purpose, and with no guarantee of completeness, accuracy, or timeliness. Individuals and organizations are responsible for assessing the impact of any actual or potential security vulnerability.

Tenable takes product security very seriously. If you believe you have found a vulnerability in one of our products, we ask that you please work with us to quickly resolve it in order to protect customers. Tenable believes in responding quickly to such reports, maintaining communication with researchers, and providing a solution in short order.

For more details on submitting vulnerability information, please see our Vulnerability Reporting Guidelines page.

If you have questions or corrections about this advisory, please email advisories@tenable.com