Skip to content

Latest commit

 

History

History
121 lines (79 loc) · 4 KB

File metadata and controls

121 lines (79 loc) · 4 KB

Exercise: Apache Struts

Unsafe deserialization leading to an RCE

CVE-2017-9805

.. rst-class:: setup

Setup

For this example you need to set up CodeQL for Visual Studio Code and download the CodeQL database for Apache Struts from GitHub.

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::ConfigSig, 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
    this.hasName("toObject")

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.
 */
module StrutsUnsafeDeserializationConfig implements ConfigSig {
  predicate isSource(Node source) {
    source.asParameter() = any(ContentTypeHandlerDeserialization des).getParameter(0)
  }
  predicate isSink(Node sink) { sink instanceof UnsafeDeserializationSink }
}
module StrutsUnsafeDeserializationFlow = Global<StrutsUnsafeDeserializationConfig>;

Model answer, step 4

import PathGraph
...
from PathNode source, PathNode sink
where StrutsUnsafeDeserializationFlow::flowPath(source, sink)
select sink.getNode().(UnsafeDeserializationSink).getMethodAccess(), source, sink, "Unsafe deserialization of $@.", source, "user input"

More full-featured version: https://github.com/github/securitylab/tree/main/CodeQL_Queries/java/Apache_Struts_CVE-2017-9805