Unsafe deserialization leading to an RCE
CVE-2017-9805
.. rst-class:: setup
For this example you need to set up CodeQL for Visual Studio Code and download the CodeQL database for Apache Struts from GitHub.
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.
Vulnerable code looked like this:
public void toObject(Reader in, Object target) { XStream xstream = createXStream(); xstream.fromXML(in, target); }
Xstream allows deserialization of dynamic proxies, which permit remote code execution.
Disclosed as CVE-2017-9805
Blog post: https://securitylab.github.com/research/apache-struts-vulnerability-cve-2017-9805
Create a class to find the interface
org.apache.struts2.rest.handler.ContentTypeHandlerHint: Use predicate
hasQualifiedName(...)Identify methods called
toObject, which are defined on direct subtypes ofContentTypeHandlerHint: Use
Method.getDeclaringType()andType.getASupertype()Implement a
DataFlow::ConfigSig, defining the source as the first parameter of atoObjectmethod, and the sink as an instance ofUnsafeDeserializationSink.Hint: Use
Node::asParameter()Construct the query as a path-problem query, and verify you find one result.
import java
/** The interface `org.apache.struts2.rest.handler.ContentTypeHandler`. */
class ContentTypeHandler extends RefType {
ContentTypeHandler() {
this.hasQualifiedName("org.apache.struts2.rest.handler", "ContentTypeHandler")
}
}/** 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")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>;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