Exercise: Apache Struts

Unsafe deserialization leading to an RCE



For this example you should download:


For this example, we will be analyzing Apache Struts.

You can also query the project in the query console on LGTM.com.

Note that results generated in the query console are likely to differ to those generated in the QL plugin as LGTM.com analyzes the most recent revisions of each project that has been added–the snapshot available to download above is based on an historical version of the codebase.

Unsafe deserialization in Struts

Apache Struts provides a ContentTypeHandler interface, which can be implemented for specific content types. It defines the following interface method:

void toObject(Reader in, Object target);

which is intended to populate the target object with data from the reader, usually through deserialization. However, the in parameter should be considered untrusted, and should not be deserialized without sanitization.

RCE in Apache Struts

Finding the RCE yourself

  1. Create a QL class to find the interface org.apache.struts2.rest.handler.ContentTypeHandler

    Hint: Use predicate hasQualifiedName(...)

  2. Identify methods called toObject, which are defined on direct subtypes of ContentTypeHandler

    Hint: Use Method.getDeclaringType() and Type.getASupertype()

  3. Implement a DataFlow::Configuration, defining the source as the first parameter of a toObject method, and the sink as an instance of UnsafeDeserializationSink.

    Hint: Use Node::asParameter()

  4. Construct the query as a path-problem query, and verify you find one result.

Model answer, step 1

import java

/** The interface `org.apache.struts2.rest.handler.ContentTypeHandler`. */

class ContentTypeHandler extends RefType {
  ContentTypeHandler() {
    this.hasQualifiedName("org.apache.struts2.rest.handler", "ContentTypeHandler")

Model answer, step 2

/** A `toObject` method on a subtype of `org.apache.struts2.rest.handler.ContentTypeHandler`. */
class ContentTypeHandlerDeserialization extends Method {
  ContentTypeHandlerDeserialization() {
    this.getDeclaringType().getASupertype() instanceof ContentTypeHandler and

Model answer, step 3

import UnsafeDeserialization
import semmle.code.java.dataflow.DataFlow::DataFlow
 * Configuration that tracks the flow of taint from the first parameter of
 * `ContentTypeHandler.toObject` to an instance of unsafe deserialization.
class StrutsUnsafeDeserializationConfig extends Configuration {
  StrutsUnsafeDeserializationConfig() { this = "StrutsUnsafeDeserializationConfig" }
  override predicate isSource(Node source) {
    source.asParameter() = any(ContentTypeHandlerDeserialization des).getParameter(0)
  override predicate isSink(Node sink) { sink instanceof UnsafeDeserializationSink }

Model answer, step 4

import PathGraph
from PathNode source, PathNode sink, StrutsUnsafeDeserializationConfig conf
where conf.hasFlowPath(source, sink)
  and sink.getNode() instanceof UnsafeDeserializationSink
select sink.getNode().(UnsafeDeserializationSink).getMethodAccess(), source, sink, "Unsafe    deserialization of $@.", source, "user input"

More full-featured version: https://github.com/Semmle/demos/tree/master/ql_demos/java/Apache_Struts_CVE-2017-9805