Add unsafe-deserialization support for Jabsorb

This is partly extracted from https://github.com/github/codeql/pull/5954
This commit is contained in:
Chris Smowton
2021-08-04 15:35:50 +01:00
parent fe654dc8ee
commit 69549e9ce3
15 changed files with 887 additions and 31 deletions

View File

@@ -15,7 +15,8 @@ may have unforeseen effects, such as the execution of arbitrary code.
<p>
There are many different serialization frameworks. This query currently
supports Kryo, XmlDecoder, XStream, SnakeYaml, JYaml, JsonIO, YAMLBeans, HessianBurlap, Castor, Burlap,
Jackson and Java IO serialization through <code>ObjectInputStream</code>/<code>ObjectOutputStream</code>.
Jackson, Jabsorb and Java IO serialization through
<code>ObjectInputStream</code>/<code>ObjectOutputStream</code>.
</p>
</overview>
@@ -100,6 +101,10 @@ Blog posts by the developer of Jackson libraries:
<a href="https://cowtowncoder.medium.com/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062">On Jackson CVEs: Dont Panic — Here is what you need to know</a>
<a href="https://cowtowncoder.medium.com/jackson-2-10-safe-default-typing-2d018f0ce2ba">Jackson 2.10: Safe Default Typing</a>
</li>
<li>
Jabsorb documentation on deserialization:
<a href="https://github.com/Servoy/jabsorb/blob/master/src/org/jabsorb/">Jabsorb JSON Serializer</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,26 @@
/**
* Provides classes for working with the Jabsorb JSON-RPC ORB framework.
*/
import java
/** The class `org.jabsorb.JSONSerializer`. */
class JabsorbSerializer extends RefType {
JabsorbSerializer() { this.hasQualifiedName("org.jabsorb", "JSONSerializer") }
}
/** The deserialization method `unmarshall`. */
class JabsorbUnmarshallMethod extends Method {
JabsorbUnmarshallMethod() {
this.getDeclaringType().getASupertype*() instanceof JabsorbSerializer and
this.getName() = "unmarshall"
}
}
/** The deserialization method `fromJSON`. */
class JabsorbFromJsonMethod extends Method {
JabsorbFromJsonMethod() {
this.getDeclaringType().getASupertype*() instanceof JabsorbSerializer and
this.getName() = "fromJSON"
}
}

View File

@@ -77,14 +77,6 @@ class SetPolymorphicTypeValidatorSource extends DataFlow::ExprNode {
}
}
/** Holds if `fromNode` to `toNode` is a dataflow step that resolves a class. */
predicate resolveClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(ReflectiveClassIdentifierMethodAccess ma |
ma.getArgument(0) = fromNode.asExpr() and
ma = toNode.asExpr()
)
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step that creates a Jackson parser.
*
@@ -153,22 +145,3 @@ predicate hasArgumentWithUnsafeJacksonAnnotation(MethodAccess call) {
hasJsonTypeInfoAnnotation(argType.(ParameterizedType).getATypeArgument())
)
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step that looks like resolving a class.
* A method probably resolves a class if it takes a string, returns a type descriptor,
* and its name contains "resolve", "load", etc.
*
* Any method call that satisfies the rule above is assumed to propagate taint from its string arguments,
* so methods that accept user-controlled data but sanitize it or use it for some
* completely different purpose before returning a type descriptor could result in false positives.
*/
predicate looksLikeResolveClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, Method m, Expr arg | m = ma.getMethod() and arg = ma.getAnArgument() |
m.getReturnType() instanceof JacksonTypeDescriptorType and
m.getName().toLowerCase().regexpMatch("(.*)(resolve|load|class|type)(.*)") and
arg.getType() instanceof TypeString and
arg = fromNode.asExpr() and
ma = toNode.asExpr()
)
}

View File

@@ -14,6 +14,7 @@ private import semmle.code.java.frameworks.YamlBeans
private import semmle.code.java.frameworks.HessianBurlap
private import semmle.code.java.frameworks.Castor
private import semmle.code.java.frameworks.Jackson
private import semmle.code.java.frameworks.Jabsorb
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.Reflection
@@ -184,6 +185,13 @@ predicate unsafeDeserialization(MethodAccess ma, Expr sink) {
hasArgumentWithUnsafeJacksonAnnotation(ma)
) and
not exists(SafeObjectMapperConfig config | config.hasFlowToExpr(ma.getQualifier()))
or
m instanceof JabsorbUnmarshallMethod and
sink = ma.getArgument(2) and
any(UnsafeTypeConfig config).hasFlowToExpr(ma.getArgument(1))
or
m instanceof JabsorbFromJsonMethod and
sink = ma.getArgument(0)
)
}
@@ -243,9 +251,36 @@ class UnsafeDeserializationConfig extends TaintTracking::Configuration {
}
}
/** Holds if `fromNode` to `toNode` is a dataflow step that resolves a class. */
predicate resolveClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(ReflectiveClassIdentifierMethodAccess ma |
ma.getArgument(0) = fromNode.asExpr() and
ma = toNode.asExpr()
)
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step that looks like resolving a class.
* A method probably resolves a class if it takes a string, returns a type descriptor,
* and its name contains "resolve", "load", etc.
*
* Any method call that satisfies the rule above is assumed to propagate taint from its string arguments,
* so methods that accept user-controlled data but sanitize it or use it for some
* completely different purpose before returning a type descriptor could result in false positives.
*/
predicate looksLikeResolveClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, Method m, Expr arg | m = ma.getMethod() and arg = ma.getAnArgument() |
m.getReturnType() instanceof JacksonTypeDescriptorType and
m.getName().toLowerCase().regexpMatch("(?i).*(resolve|load|class|type).*") and
arg.getType() instanceof TypeString and
arg = fromNode.asExpr() and
ma = toNode.asExpr()
)
}
/**
* Tracks flow from a remote source to a type descriptor (e.g. a `java.lang.Class` instance)
* passed to a Jackson deserialization method.
* passed to a deserialization method.
*
* If this is user-controlled, arbitrary code could be executed while instantiating the user-specified type.
*/
@@ -256,7 +291,12 @@ class UnsafeTypeConfig extends TaintTracking2::Configuration {
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma, int i, Expr arg | i > 0 and ma.getArgument(i) = arg |
ma.getMethod() instanceof ObjectMapperReadMethod and
(
ma.getMethod() instanceof ObjectMapperReadMethod
or
ma.getMethod() instanceof JabsorbUnmarshallMethod
) and
// Note `JacksonTypeDescriptorType` includes plain old `java.lang.Class`
arg.getType() instanceof JacksonTypeDescriptorType and
arg = sink.asExpr()
)