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.

You can download the database as a zip file by clicking the link on the slide above. To use the database in CodeQL for Visual Studio Code:

  1. Unzip the file
  2. Add the unzipped database to Visual Studio Code
  3. Upgrade the database if necessary

For further information, see Using the extension in the CodeQL for Visual Studio Code help.

Note that results generated in the query console are likely to differ to those generated in CodeQL for Visual Studio Code as LGTM.com analyzes the most recent revisions of each project that has been added–the CodeQL database 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 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