Merge pull request #5900 from artem-smotrakov/unsafe-jackson-deserialization

Java: Unsafe deserialization with Jackson
This commit is contained in:
Anders Schack-Mulligen
2021-08-02 12:45:30 +02:00
committed by GitHub
28 changed files with 892 additions and 500 deletions

View File

@@ -14,8 +14,8 @@ may have unforeseen effects, such as the execution of arbitrary code.
</p>
<p>
There are many different serialization frameworks. This query currently
supports Kryo, XmlDecoder, XStream, SnakeYaml, JYaml, JsonIO, YAMLBeans, HessianBurlap, Castor, Burlap
and Java IO serialization through <code>ObjectInputStream</code>/<code>ObjectOutputStream</code>.
supports Kryo, XmlDecoder, XStream, SnakeYaml, JYaml, JsonIO, YAMLBeans, HessianBurlap, Castor, Burlap,
Jackson and Java IO serialization through <code>ObjectInputStream</code>/<code>ObjectOutputStream</code>.
</p>
</overview>
@@ -91,6 +91,15 @@ Remote code execution in JYaml library:
JsonIO deserialization vulnerabilities:
<a href="https://klezvirus.github.io/Advanced-Web-Hacking/Serialisation/">JsonIO deserialization</a>.
</li>
<li>
Research by Moritz Bechler:
<a href="https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true">Java Unmarshaller Security - Turning your data into code execution</a>
</li>
<li>
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>
</references>
</qhelp>

View File

@@ -12,51 +12,9 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.UnsafeDeserialization
import semmle.code.java.security.UnsafeDeserializationQuery
import DataFlow::PathGraph
class UnsafeDeserializationConfig extends TaintTracking::Configuration {
UnsafeDeserializationConfig() { this = "UnsafeDeserializationConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeDeserializationSink }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(ClassInstanceExpr cie |
cie.getArgument(0) = pred.asExpr() and
cie = succ.asExpr() and
(
cie.getConstructor().getDeclaringType() instanceof JsonIoJsonReader or
cie.getConstructor().getDeclaringType() instanceof YamlBeansReader or
cie.getConstructor().getDeclaringType().getASupertype*() instanceof UnsafeHessianInput or
cie.getConstructor().getDeclaringType() instanceof BurlapInput
)
)
or
exists(MethodAccess ma |
ma.getMethod() instanceof BurlapInputInitMethod and
ma.getArgument(0) = pred.asExpr() and
ma.getQualifier() = succ.asExpr()
)
}
override predicate isSanitizer(DataFlow::Node node) {
exists(ClassInstanceExpr cie |
cie.getConstructor().getDeclaringType() instanceof JsonIoJsonReader and
cie = node.asExpr() and
exists(SafeJsonIoConfig sji | sji.hasFlowToExpr(cie.getArgument(1)))
)
or
exists(MethodAccess ma |
ma.getMethod() instanceof JsonIoJsonToJavaMethod and
ma.getArgument(0) = node.asExpr() and
exists(SafeJsonIoConfig sji | sji.hasFlowToExpr(ma.getArgument(1)))
)
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, UnsafeDeserializationConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode().(UnsafeDeserializationSink).getMethodAccess(), source, sink,

View File

@@ -0,0 +1,174 @@
/**
* Provides classes and predicates for working with the Jackson serialization framework.
*/
import java
private import semmle.code.java.Reflection
private import semmle.code.java.dataflow.DataFlow
private class ObjectMapper extends RefType {
ObjectMapper() {
getASupertype*().hasQualifiedName("com.fasterxml.jackson.databind", "ObjectMapper")
}
}
/** A builder for building Jackson's `JsonMapper`. */
class MapperBuilder extends RefType {
MapperBuilder() {
hasQualifiedName("com.fasterxml.jackson.databind.cfg", "MapperBuilder<JsonMapper,Builder>")
}
}
private class JsonFactory extends RefType {
JsonFactory() { hasQualifiedName("com.fasterxml.jackson.core", "JsonFactory") }
}
private class JsonParser extends RefType {
JsonParser() { hasQualifiedName("com.fasterxml.jackson.core", "JsonParser") }
}
/** A type descriptor in Jackson libraries. For example, `java.lang.Class`. */
class JacksonTypeDescriptorType extends RefType {
JacksonTypeDescriptorType() {
this instanceof TypeClass or
hasQualifiedName("com.fasterxml.jackson.databind", "JavaType") or
hasQualifiedName("com.fasterxml.jackson.core.type", "TypeReference")
}
}
/** A method in `ObjectMapper` that deserialize data. */
class ObjectMapperReadMethod extends Method {
ObjectMapperReadMethod() {
this.getDeclaringType() instanceof ObjectMapper and
this.hasName(["readValue", "readValues", "treeToValue"])
}
}
/** A call that enables the default typing in `ObjectMapper`. */
class EnableJacksonDefaultTyping extends MethodAccess {
EnableJacksonDefaultTyping() {
this.getMethod().getDeclaringType() instanceof ObjectMapper and
this.getMethod().hasName("enableDefaultTyping")
}
}
/** A qualifier of a call to one of the methods in `ObjectMapper` that deserialize data. */
class ObjectMapperReadQualifier extends DataFlow::ExprNode {
ObjectMapperReadQualifier() {
exists(MethodAccess ma | ma.getQualifier() = this.asExpr() |
ma.getMethod() instanceof ObjectMapperReadMethod
)
}
}
/** A source that sets a type validator. */
class SetPolymorphicTypeValidatorSource extends DataFlow::ExprNode {
SetPolymorphicTypeValidatorSource() {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
(
m.getDeclaringType() instanceof ObjectMapper and
m.hasName("setPolymorphicTypeValidator")
or
m.getDeclaringType() instanceof MapperBuilder and
m.hasName("polymorphicTypeValidator")
) and
this.asExpr() = ma.getQualifier()
)
}
}
/** 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.
*
* For example, a `createParser(userString)` call yields a `JsonParser`, which becomes dangerous
* if passed to an unsafely-configured `ObjectMapper`'s `readValue` method.
*/
predicate createJacksonJsonParserStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
(m.getDeclaringType() instanceof ObjectMapper or m.getDeclaringType() instanceof JsonFactory) and
m.hasName("createParser") and
ma.getArgument(0) = fromNode.asExpr() and
ma = toNode.asExpr()
)
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step that creates a Jackson `TreeNode`.
*
* These are parse trees of user-supplied JSON, which may lead to arbitrary code execution
* if passed to an unsafely-configured `ObjectMapper`'s `treeToValue` method.
*/
predicate createJacksonTreeNodeStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
m.getDeclaringType() instanceof ObjectMapper and
m.hasName("readTree") and
ma.getArgument(0) = fromNode.asExpr() and
ma = toNode.asExpr()
)
or
exists(MethodAccess ma, Method m | m = ma.getMethod() |
m.getDeclaringType() instanceof JsonParser and
m.hasName("readValueAsTree") and
ma.getQualifier() = fromNode.asExpr() and
ma = toNode.asExpr()
)
}
/**
* Holds if `type` or one of its supertypes has a field with `JsonTypeInfo` annotation
* that enables polymorphic type handling.
*/
private predicate hasJsonTypeInfoAnnotation(RefType type) {
hasFieldWithJsonTypeAnnotation(type.getASupertype*()) or
hasJsonTypeInfoAnnotation(type.getAField().getType())
}
/**
* Holds if `type` has a field with `JsonTypeInfo` annotation
* that enables polymorphic type handling.
*/
private predicate hasFieldWithJsonTypeAnnotation(RefType type) {
exists(Annotation a |
type.getAField().getAnAnnotation() = a and
a.getType().hasQualifiedName("com.fasterxml.jackson.annotation", "JsonTypeInfo") and
a.getValue("use").(VarAccess).getVariable().hasName(["CLASS", "MINIMAL_CLASS"])
)
}
/**
* Holds if `call` is a method call to a Jackson deserialization method such as `ObjectMapper.readValue(String, Class)`,
* and the target deserialized class has a field with a `JsonTypeInfo` annotation that enables polymorphic typing.
*/
predicate hasArgumentWithUnsafeJacksonAnnotation(MethodAccess call) {
call.getMethod() instanceof ObjectMapperReadMethod and
exists(RefType argType, int i | i > 0 and argType = call.getArgument(i).getType() |
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

@@ -1,172 +0,0 @@
import semmle.code.java.frameworks.Kryo
import semmle.code.java.frameworks.XStream
import semmle.code.java.frameworks.SnakeYaml
import semmle.code.java.frameworks.FastJson
import semmle.code.java.frameworks.JYaml
import semmle.code.java.frameworks.JsonIo
import semmle.code.java.frameworks.YamlBeans
import semmle.code.java.frameworks.HessianBurlap
import semmle.code.java.frameworks.Castor
import semmle.code.java.frameworks.apache.Lang
class ObjectInputStreamReadObjectMethod extends Method {
ObjectInputStreamReadObjectMethod() {
this.getDeclaringType().getASourceSupertype*().hasQualifiedName("java.io", "ObjectInputStream") and
(this.hasName("readObject") or this.hasName("readUnshared"))
}
}
class XMLDecoderReadObjectMethod extends Method {
XMLDecoderReadObjectMethod() {
this.getDeclaringType().hasQualifiedName("java.beans", "XMLDecoder") and
this.hasName("readObject")
}
}
class SafeXStream extends DataFlow2::Configuration {
SafeXStream() { this = "UnsafeDeserialization::SafeXStream" }
override predicate isSource(DataFlow::Node src) {
any(XStreamEnableWhiteListing ma).getQualifier().(VarAccess).getVariable().getAnAccess() =
src.asExpr()
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
sink.asExpr() = ma.getQualifier() and
ma.getMethod() instanceof XStreamReadObjectMethod
)
}
}
class SafeKryo extends DataFlow2::Configuration {
SafeKryo() { this = "UnsafeDeserialization::SafeKryo" }
override predicate isSource(DataFlow::Node src) {
any(KryoEnableWhiteListing ma).getQualifier().(VarAccess).getVariable().getAnAccess() =
src.asExpr()
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
sink.asExpr() = ma.getQualifier() and
ma.getMethod() instanceof KryoReadObjectMethod
)
}
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
stepKryoPoolBuilderFactoryArgToConstructor(node1, node2) or
stepKryoPoolRunMethodAccessQualifierToFunctionalArgument(node1, node2) or
stepKryoPoolBuilderChainMethod(node1, node2) or
stepKryoPoolBorrowMethod(node1, node2)
}
/**
* Holds when a functional expression is used to create a `KryoPool.Builder`.
* Eg. `new KryoPool.Builder(() -> new Kryo())`
*/
private predicate stepKryoPoolBuilderFactoryArgToConstructor(
DataFlow::Node node1, DataFlow::Node node2
) {
exists(ConstructorCall cc, FunctionalExpr fe |
cc.getConstructedType() instanceof KryoPoolBuilder and
fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and
node2.asExpr() = cc and
cc.getArgument(0) = fe
)
}
/**
* Holds when a `KryoPool.run` is called to use a `Kryo` instance.
* Eg. `pool.run(kryo -> ...)`
*/
private predicate stepKryoPoolRunMethodAccessQualifierToFunctionalArgument(
DataFlow::Node node1, DataFlow::Node node2
) {
exists(MethodAccess ma |
ma.getMethod() instanceof KryoPoolRunMethod and
node1.asExpr() = ma.getQualifier() and
ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(0) = node2.asParameter()
)
}
/**
* Holds when a `KryoPool.Builder` method is called fluently.
*/
private predicate stepKryoPoolBuilderChainMethod(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma |
ma.getMethod() instanceof KryoPoolBuilderMethod and
ma = node2.asExpr() and
ma.getQualifier() = node1.asExpr()
)
}
/**
* Holds when a `KryoPool.borrow` method is called.
*/
private predicate stepKryoPoolBorrowMethod(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma |
ma.getMethod() =
any(Method m | m.getDeclaringType() instanceof KryoPool and m.hasName("borrow")) and
node1.asExpr() = ma.getQualifier() and
node2.asExpr() = ma
)
}
}
predicate unsafeDeserialization(MethodAccess ma, Expr sink) {
exists(Method m | m = ma.getMethod() |
m instanceof ObjectInputStreamReadObjectMethod and
sink = ma.getQualifier() and
not exists(DataFlow::ExprNode node |
node.getExpr() = sink and
node.getTypeBound()
.(RefType)
.hasQualifiedName("org.apache.commons.io.serialization", "ValidatingObjectInputStream")
)
or
m instanceof XMLDecoderReadObjectMethod and
sink = ma.getQualifier()
or
m instanceof XStreamReadObjectMethod and
sink = ma.getAnArgument() and
not exists(SafeXStream sxs | sxs.hasFlowToExpr(ma.getQualifier()))
or
m instanceof KryoReadObjectMethod and
sink = ma.getAnArgument() and
not exists(SafeKryo sk | sk.hasFlowToExpr(ma.getQualifier()))
or
m instanceof MethodApacheSerializationUtilsDeserialize and
sink = ma.getArgument(0)
or
ma instanceof UnsafeSnakeYamlParse and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof FastJsonParseMethod and
not fastJsonLooksSafe() and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof JYamlLoaderUnsafeLoadMethod and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof JsonIoJsonToJavaMethod and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof JsonIoReadObjectMethod and
sink = ma.getQualifier()
or
ma.getMethod() instanceof YamlBeansReaderReadMethod and sink = ma.getQualifier()
or
ma.getMethod() instanceof UnsafeHessianInputReadObjectMethod and sink = ma.getQualifier()
or
ma.getMethod() instanceof CastorUnmarshalMethod and sink = ma.getAnArgument()
or
ma.getMethod() instanceof BurlapInputReadObjectMethod and sink = ma.getQualifier()
)
}
class UnsafeDeserializationSink extends DataFlow::ExprNode {
UnsafeDeserializationSink() { unsafeDeserialization(_, this.getExpr()) }
MethodAccess getMethodAccess() { unsafeDeserialization(result, this.getExpr()) }
}

View File

@@ -0,0 +1,318 @@
/**
* Provides classes and predicates for finding deserialization vulnerabilities.
*/
import semmle.code.java.dataflow.FlowSources
private import semmle.code.java.dataflow.TaintTracking2
private import semmle.code.java.frameworks.Kryo
private import semmle.code.java.frameworks.XStream
private import semmle.code.java.frameworks.SnakeYaml
private import semmle.code.java.frameworks.FastJson
private import semmle.code.java.frameworks.JYaml
private import semmle.code.java.frameworks.JsonIo
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.apache.Lang
private import semmle.code.java.Reflection
private class ObjectInputStreamReadObjectMethod extends Method {
ObjectInputStreamReadObjectMethod() {
this.getDeclaringType().getASourceSupertype*().hasQualifiedName("java.io", "ObjectInputStream") and
(this.hasName("readObject") or this.hasName("readUnshared"))
}
}
private class XMLDecoderReadObjectMethod extends Method {
XMLDecoderReadObjectMethod() {
this.getDeclaringType().hasQualifiedName("java.beans", "XMLDecoder") and
this.hasName("readObject")
}
}
private class SafeXStream extends DataFlow2::Configuration {
SafeXStream() { this = "UnsafeDeserialization::SafeXStream" }
override predicate isSource(DataFlow::Node src) {
any(XStreamEnableWhiteListing ma).getQualifier().(VarAccess).getVariable().getAnAccess() =
src.asExpr()
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
sink.asExpr() = ma.getQualifier() and
ma.getMethod() instanceof XStreamReadObjectMethod
)
}
}
private class SafeKryo extends DataFlow2::Configuration {
SafeKryo() { this = "UnsafeDeserialization::SafeKryo" }
override predicate isSource(DataFlow::Node src) {
any(KryoEnableWhiteListing ma).getQualifier().(VarAccess).getVariable().getAnAccess() =
src.asExpr()
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
sink.asExpr() = ma.getQualifier() and
ma.getMethod() instanceof KryoReadObjectMethod
)
}
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
stepKryoPoolBuilderFactoryArgToConstructor(node1, node2) or
stepKryoPoolRunMethodAccessQualifierToFunctionalArgument(node1, node2) or
stepKryoPoolBuilderChainMethod(node1, node2) or
stepKryoPoolBorrowMethod(node1, node2)
}
/**
* Holds when a functional expression is used to create a `KryoPool.Builder`.
* Eg. `new KryoPool.Builder(() -> new Kryo())`
*/
private predicate stepKryoPoolBuilderFactoryArgToConstructor(
DataFlow::Node node1, DataFlow::Node node2
) {
exists(ConstructorCall cc, FunctionalExpr fe |
cc.getConstructedType() instanceof KryoPoolBuilder and
fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and
node2.asExpr() = cc and
cc.getArgument(0) = fe
)
}
/**
* Holds when a `KryoPool.run` is called to use a `Kryo` instance.
* Eg. `pool.run(kryo -> ...)`
*/
private predicate stepKryoPoolRunMethodAccessQualifierToFunctionalArgument(
DataFlow::Node node1, DataFlow::Node node2
) {
exists(MethodAccess ma |
ma.getMethod() instanceof KryoPoolRunMethod and
node1.asExpr() = ma.getQualifier() and
ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(0) = node2.asParameter()
)
}
/**
* Holds when a `KryoPool.Builder` method is called fluently.
*/
private predicate stepKryoPoolBuilderChainMethod(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma |
ma.getMethod() instanceof KryoPoolBuilderMethod and
ma = node2.asExpr() and
ma.getQualifier() = node1.asExpr()
)
}
/**
* Holds when a `KryoPool.borrow` method is called.
*/
private predicate stepKryoPoolBorrowMethod(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma |
ma.getMethod() =
any(Method m | m.getDeclaringType() instanceof KryoPool and m.hasName("borrow")) and
node1.asExpr() = ma.getQualifier() and
node2.asExpr() = ma
)
}
}
/**
* Holds if `ma` is a call that deserializes data from `sink`.
*/
predicate unsafeDeserialization(MethodAccess ma, Expr sink) {
exists(Method m | m = ma.getMethod() |
m instanceof ObjectInputStreamReadObjectMethod and
sink = ma.getQualifier() and
not exists(DataFlow::ExprNode node |
node.getExpr() = sink and
node.getTypeBound()
.(RefType)
.hasQualifiedName("org.apache.commons.io.serialization", "ValidatingObjectInputStream")
)
or
m instanceof XMLDecoderReadObjectMethod and
sink = ma.getQualifier()
or
m instanceof XStreamReadObjectMethod and
sink = ma.getAnArgument() and
not exists(SafeXStream sxs | sxs.hasFlowToExpr(ma.getQualifier()))
or
m instanceof KryoReadObjectMethod and
sink = ma.getAnArgument() and
not exists(SafeKryo sk | sk.hasFlowToExpr(ma.getQualifier()))
or
m instanceof MethodApacheSerializationUtilsDeserialize and
sink = ma.getArgument(0)
or
ma instanceof UnsafeSnakeYamlParse and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof FastJsonParseMethod and
not fastJsonLooksSafe() and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof JYamlLoaderUnsafeLoadMethod and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof JsonIoJsonToJavaMethod and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof JsonIoReadObjectMethod and
sink = ma.getQualifier()
or
ma.getMethod() instanceof YamlBeansReaderReadMethod and sink = ma.getQualifier()
or
ma.getMethod() instanceof UnsafeHessianInputReadObjectMethod and sink = ma.getQualifier()
or
ma.getMethod() instanceof CastorUnmarshalMethod and sink = ma.getAnArgument()
or
ma.getMethod() instanceof BurlapInputReadObjectMethod and sink = ma.getQualifier()
or
ma.getMethod() instanceof ObjectMapperReadMethod and
sink = ma.getArgument(0) and
(
exists(UnsafeTypeConfig config | config.hasFlowToExpr(ma.getAnArgument()))
or
exists(EnableJacksonDefaultTypingConfig config | config.hasFlowToExpr(ma.getQualifier()))
or
hasArgumentWithUnsafeJacksonAnnotation(ma)
) and
not exists(SafeObjectMapperConfig config | config.hasFlowToExpr(ma.getQualifier()))
)
}
/** A sink for unsafe deserialization. */
class UnsafeDeserializationSink extends DataFlow::ExprNode {
UnsafeDeserializationSink() { unsafeDeserialization(_, this.getExpr()) }
/** Gets a call that triggers unsafe deserialization. */
MethodAccess getMethodAccess() { unsafeDeserialization(result, this.getExpr()) }
}
/**
* Tracks flows from remote user input to a deserialization sink.
*/
class UnsafeDeserializationConfig extends TaintTracking::Configuration {
UnsafeDeserializationConfig() { this = "UnsafeDeserializationConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeDeserializationSink }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(ClassInstanceExpr cie |
cie.getArgument(0) = pred.asExpr() and
cie = succ.asExpr() and
(
cie.getConstructor().getDeclaringType() instanceof JsonIoJsonReader or
cie.getConstructor().getDeclaringType() instanceof YamlBeansReader or
cie.getConstructor().getDeclaringType().getASupertype*() instanceof UnsafeHessianInput or
cie.getConstructor().getDeclaringType() instanceof BurlapInput
)
)
or
exists(MethodAccess ma |
ma.getMethod() instanceof BurlapInputInitMethod and
ma.getArgument(0) = pred.asExpr() and
ma.getQualifier() = succ.asExpr()
)
or
createJacksonJsonParserStep(pred, succ)
or
createJacksonTreeNodeStep(pred, succ)
}
override predicate isSanitizer(DataFlow::Node node) {
exists(ClassInstanceExpr cie |
cie.getConstructor().getDeclaringType() instanceof JsonIoJsonReader and
cie = node.asExpr() and
exists(SafeJsonIoConfig sji | sji.hasFlowToExpr(cie.getArgument(1)))
)
or
exists(MethodAccess ma |
ma.getMethod() instanceof JsonIoJsonToJavaMethod and
ma.getArgument(0) = node.asExpr() and
exists(SafeJsonIoConfig sji | sji.hasFlowToExpr(ma.getArgument(1)))
)
}
}
/**
* Tracks flow from a remote source to a type descriptor (e.g. a `java.lang.Class` instance)
* passed to a Jackson deserialization method.
*
* If this is user-controlled, arbitrary code could be executed while instantiating the user-specified type.
*/
class UnsafeTypeConfig extends TaintTracking2::Configuration {
UnsafeTypeConfig() { this = "UnsafeTypeConfig" }
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
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
arg.getType() instanceof JacksonTypeDescriptorType and
arg = sink.asExpr()
)
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step that resolves a class
* or at least looks like resolving a class.
*/
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
resolveClassStep(fromNode, toNode) or
looksLikeResolveClassStep(fromNode, toNode)
}
}
/**
* Tracks flow from `enableDefaultTyping` calls to a subsequent Jackson deserialization method call.
*/
class EnableJacksonDefaultTypingConfig extends DataFlow2::Configuration {
EnableJacksonDefaultTypingConfig() { this = "EnableJacksonDefaultTypingConfig" }
override predicate isSource(DataFlow::Node src) {
any(EnableJacksonDefaultTyping ma).getQualifier() = src.asExpr()
}
override predicate isSink(DataFlow::Node sink) { sink instanceof ObjectMapperReadQualifier }
}
/**
* Tracks flow from calls that set a type validator to a subsequent Jackson deserialization method call,
* including across builder method calls.
*
* Such a Jackson deserialization method call is safe because validation will likely prevent instantiating unexpected types.
*/
class SafeObjectMapperConfig extends DataFlow2::Configuration {
SafeObjectMapperConfig() { this = "SafeObjectMapperConfig" }
override predicate isSource(DataFlow::Node src) {
src instanceof SetPolymorphicTypeValidatorSource
}
override predicate isSink(DataFlow::Node sink) { sink instanceof ObjectMapperReadQualifier }
/**
* Holds if `fromNode` to `toNode` is a dataflow step
* that configures or creates an `ObjectMapper` via a builder.
*/
override predicate isAdditionalFlowStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
m.getDeclaringType() instanceof MapperBuilder and
m.getReturnType()
.(RefType)
.hasQualifiedName("com.fasterxml.jackson.databind.json",
["JsonMapper$Builder", "JsonMapper"]) and
fromNode.asExpr() = ma.getQualifier() and
ma = toNode.asExpr()
)
}
}