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:
java.io.EOFException
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:
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try
{ 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));
try
{ return doReadRemoteInvocation(ois); }
finally
{ 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.