diff --git a/java/change-notes/2021-05-17-jackson-deserialization-sink.md b/java/change-notes/2021-05-17-jackson-deserialization-sink.md new file mode 100644 index 000000000000..6db65d9dba41 --- /dev/null +++ b/java/change-notes/2021-05-17-jackson-deserialization-sink.md @@ -0,0 +1,3 @@ +lgtm,codescanning +* The "Deserialization of user-controlled data" (`java/unsafe-deserialization`) query + now recognizes `Jackson` deserialization. diff --git a/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.qhelp b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.qhelp index e9600f11b937..c580d36e2cc4 100644 --- a/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.qhelp +++ b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.qhelp @@ -14,8 +14,8 @@ may have unforeseen effects, such as the execution of arbitrary code.

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 ObjectInputStream/ObjectOutputStream. +supports Kryo, XmlDecoder, XStream, SnakeYaml, JYaml, JsonIO, YAMLBeans, HessianBurlap, Castor, Burlap, +Jackson and Java IO serialization through ObjectInputStream/ObjectOutputStream.

@@ -91,6 +91,15 @@ Remote code execution in JYaml library: JsonIO deserialization vulnerabilities: JsonIO deserialization. +
  • +Research by Moritz Bechler: +Java Unmarshaller Security - Turning your data into code execution +
  • +
  • +Blog posts by the developer of Jackson libraries: +On Jackson CVEs: Don’t Panic — Here is what you need to know +Jackson 2.10: Safe Default Typing +
  • diff --git a/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.ql b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.ql index 606468d451d1..6bb4cdc3561d 100644 --- a/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.ql +++ b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.ql @@ -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, diff --git a/java/ql/src/semmle/code/java/frameworks/Jackson.qll b/java/ql/src/semmle/code/java/frameworks/Jackson.qll new file mode 100644 index 000000000000..505a8e9cdf01 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/Jackson.qll @@ -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") + } +} + +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() + ) +} diff --git a/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll b/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll deleted file mode 100644 index d035837ad6a0..000000000000 --- a/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll +++ /dev/null @@ -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()) } -} diff --git a/java/ql/src/semmle/code/java/security/UnsafeDeserializationQuery.qll b/java/ql/src/semmle/code/java/security/UnsafeDeserializationQuery.qll new file mode 100644 index 000000000000..b9cfa1ddde8b --- /dev/null +++ b/java/ql/src/semmle/code/java/security/UnsafeDeserializationQuery.qll @@ -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() + ) + } +} diff --git a/java/ql/test/library-tests/UnsafeDeserialization/unsafeDeserialization.ql b/java/ql/test/library-tests/UnsafeDeserialization/unsafeDeserialization.ql index 9433eba7f7f3..0e0217a2472d 100644 --- a/java/ql/test/library-tests/UnsafeDeserialization/unsafeDeserialization.ql +++ b/java/ql/test/library-tests/UnsafeDeserialization/unsafeDeserialization.ql @@ -1,5 +1,5 @@ import default -import semmle.code.java.security.UnsafeDeserialization +import semmle.code.java.security.UnsafeDeserializationQuery from Method m, MethodAccess ma where ma.getMethod() = m and unsafeDeserialization(ma, _) diff --git a/java/ql/test/query-tests/security/CWE-502/A.java b/java/ql/test/query-tests/security/CWE-502/A.java index 50090f69132e..e95f15bff4f8 100644 --- a/java/ql/test/query-tests/security/CWE-502/A.java +++ b/java/ql/test/query-tests/security/CWE-502/A.java @@ -12,34 +12,34 @@ public class A { public Object deserialize1(Socket sock) throws java.io.IOException, ClassNotFoundException { InputStream inputStream = sock.getInputStream(); ObjectInputStream in = new ObjectInputStream(inputStream); - return in.readObject(); // unsafe + return in.readObject(); // $unsafeDeserialization } public Object deserialize2(Socket sock) throws java.io.IOException, ClassNotFoundException { InputStream inputStream = sock.getInputStream(); ObjectInputStream in = new ObjectInputStream(inputStream); - return in.readUnshared(); // unsafe + return in.readUnshared(); // $unsafeDeserialization } public Object deserialize3(Socket sock) throws java.io.IOException { InputStream inputStream = sock.getInputStream(); XMLDecoder d = new XMLDecoder(inputStream); - return d.readObject(); // unsafe + return d.readObject(); // $unsafeDeserialization } public Object deserialize4(Socket sock) throws java.io.IOException { XStream xs = new XStream(); InputStream inputStream = sock.getInputStream(); Reader reader = new InputStreamReader(inputStream); - return xs.fromXML(reader); // unsafe + return xs.fromXML(reader); // $unsafeDeserialization } public void deserialize5(Socket sock) throws java.io.IOException { Kryo kryo = new Kryo(); Input input = new Input(sock.getInputStream()); - A a1 = kryo.readObject(input, A.class); // unsafe - A a2 = kryo.readObjectOrNull(input, A.class); // unsafe - Object o = kryo.readClassAndObject(input); // unsafe + A a1 = kryo.readObject(input, A.class); // $unsafeDeserialization + A a2 = kryo.readObjectOrNull(input, A.class); // $unsafeDeserialization + Object o = kryo.readClassAndObject(input); // $unsafeDeserialization } private Kryo getSafeKryo() throws java.io.IOException { @@ -58,21 +58,21 @@ public void deserialize6(Socket sock) throws java.io.IOException { public void deserializeSnakeYaml(Socket sock) throws java.io.IOException { Yaml yaml = new Yaml(); InputStream input = sock.getInputStream(); - Object o = yaml.load(input); //unsafe - Object o2 = yaml.loadAll(input); //unsafe - Object o3 = yaml.parse(new InputStreamReader(input)); //unsafe - A o4 = yaml.loadAs(input, A.class); //unsafe - A o5 = yaml.loadAs(new InputStreamReader(input), A.class); //unsafe + Object o = yaml.load(input); // $unsafeDeserialization + Object o2 = yaml.loadAll(input); // $unsafeDeserialization + Object o3 = yaml.parse(new InputStreamReader(input)); // $unsafeDeserialization + A o4 = yaml.loadAs(input, A.class); // $unsafeDeserialization + A o5 = yaml.loadAs(new InputStreamReader(input), A.class); // $unsafeDeserialization } public void deserializeSnakeYaml2(Socket sock) throws java.io.IOException { Yaml yaml = new Yaml(new Constructor()); InputStream input = sock.getInputStream(); - Object o = yaml.load(input); //unsafe - Object o2 = yaml.loadAll(input); //unsafe - Object o3 = yaml.parse(new InputStreamReader(input)); //unsafe - A o4 = yaml.loadAs(input, A.class); //unsafe - A o5 = yaml.loadAs(new InputStreamReader(input), A.class); //unsafe + Object o = yaml.load(input); // $unsafeDeserialization + Object o2 = yaml.loadAll(input); // $unsafeDeserialization + Object o3 = yaml.parse(new InputStreamReader(input)); // $unsafeDeserialization + A o4 = yaml.loadAs(input, A.class); // $unsafeDeserialization + A o5 = yaml.loadAs(new InputStreamReader(input), A.class); // $unsafeDeserialization } public void deserializeSnakeYaml3(Socket sock) throws java.io.IOException { @@ -88,10 +88,10 @@ public void deserializeSnakeYaml3(Socket sock) throws java.io.IOException { public void deserializeSnakeYaml4(Socket sock) throws java.io.IOException { Yaml yaml = new Yaml(new Constructor(A.class)); InputStream input = sock.getInputStream(); - Object o = yaml.load(input); //unsafe - Object o2 = yaml.loadAll(input); //unsafe - Object o3 = yaml.parse(new InputStreamReader(input)); //unsafe - A o4 = yaml.loadAs(input, A.class); //unsafe - A o5 = yaml.loadAs(new InputStreamReader(input), A.class); //unsafe + Object o = yaml.load(input); // $unsafeDeserialization + Object o2 = yaml.loadAll(input); // $unsafeDeserialization + Object o3 = yaml.parse(new InputStreamReader(input)); // $unsafeDeserialization + A o4 = yaml.loadAs(input, A.class); // $unsafeDeserialization + A o5 = yaml.loadAs(new InputStreamReader(input), A.class); // $unsafeDeserialization } } diff --git a/java/ql/test/query-tests/security/CWE-502/B.java b/java/ql/test/query-tests/security/CWE-502/B.java index a12555edd701..d97a44cfd58a 100644 --- a/java/ql/test/query-tests/security/CWE-502/B.java +++ b/java/ql/test/query-tests/security/CWE-502/B.java @@ -5,14 +5,14 @@ public class B { public Object deserializeJson1(Socket sock) throws java.io.IOException { InputStream inputStream = sock.getInputStream(); - return JSON.parseObject(inputStream, null); // unsafe + return JSON.parseObject(inputStream, null); // $unsafeDeserialization } public Object deserializeJson2(Socket sock) throws java.io.IOException { InputStream inputStream = sock.getInputStream(); byte[] bytes = new byte[100]; inputStream.read(bytes); - return JSON.parse(bytes); // unsafe + return JSON.parse(bytes); // $unsafeDeserialization } public Object deserializeJson3(Socket sock) throws java.io.IOException { @@ -20,7 +20,7 @@ public Object deserializeJson3(Socket sock) throws java.io.IOException { byte[] bytes = new byte[100]; inputStream.read(bytes); String s = new String(bytes); - return JSON.parseObject(s); // unsafe + return JSON.parseObject(s); // $unsafeDeserialization } public Object deserializeJson4(Socket sock) throws java.io.IOException { @@ -28,6 +28,6 @@ public Object deserializeJson4(Socket sock) throws java.io.IOException { byte[] bytes = new byte[100]; inputStream.read(bytes); String s = new String(bytes); - return JSON.parse(s); // unsafe + return JSON.parse(s); // $unsafeDeserialization } } diff --git a/java/ql/test/query-tests/security/CWE-502/C.java b/java/ql/test/query-tests/security/CWE-502/C.java index bae0ca8ceaec..e13ae98ead52 100644 --- a/java/ql/test/query-tests/security/CWE-502/C.java +++ b/java/ql/test/query-tests/security/CWE-502/C.java @@ -21,16 +21,16 @@ public class C { @GetMapping(value = "jyaml") public void bad1(HttpServletRequest request) throws Exception { String data = request.getParameter("data"); - Yaml.load(data); //bad - Yaml.loadStream(data); //bad - Yaml.loadStreamOfType(data, Object.class); //bad - Yaml.loadType(data, Object.class); //bad + Yaml.load(data); // $unsafeDeserialization + Yaml.loadStream(data); // $unsafeDeserialization + Yaml.loadStreamOfType(data, Object.class); // $unsafeDeserialization + Yaml.loadType(data, Object.class); // $unsafeDeserialization org.ho.yaml.YamlConfig yamlConfig = new YamlConfig(); - yamlConfig.load(data); //bad - yamlConfig.loadStream(data); //bad - yamlConfig.loadStreamOfType(data, Object.class); //bad - yamlConfig.loadType(data, Object.class); //bad + yamlConfig.load(data); // $unsafeDeserialization + yamlConfig.loadStream(data); // $unsafeDeserialization + yamlConfig.loadStreamOfType(data, Object.class); // $unsafeDeserialization + yamlConfig.loadType(data, Object.class); // $unsafeDeserialization } @GetMapping(value = "jsonio") @@ -40,19 +40,19 @@ public void bad2(HttpServletRequest request) { HashMap hashMap = new HashMap(); hashMap.put("USE_MAPS", true); - JsonReader.jsonToJava(data); //bad + JsonReader.jsonToJava(data); // $unsafeDeserialization - JsonReader jr = new JsonReader(data, null); //bad - jr.readObject(); + JsonReader jr = new JsonReader(data, null); + jr.readObject(); // $unsafeDeserialization } @GetMapping(value = "yamlbeans") public void bad3(HttpServletRequest request) throws Exception { String data = request.getParameter("data"); YamlReader r = new YamlReader(data); - r.read(); //bad - r.read(Object.class); //bad - r.read(Object.class, Object.class); //bad + r.read(); // $unsafeDeserialization + r.read(Object.class); // $unsafeDeserialization + r.read(Object.class, Object.class); // $unsafeDeserialization } @GetMapping(value = "hessian") @@ -60,8 +60,8 @@ public void bad4(HttpServletRequest request) throws Exception { byte[] bytes = request.getParameter("data").getBytes(); ByteArrayInputStream bis = new ByteArrayInputStream(bytes); HessianInput hessianInput = new HessianInput(bis); - hessianInput.readObject(); //bad - hessianInput.readObject(Object.class); //bad + hessianInput.readObject(); // $unsafeDeserialization + hessianInput.readObject(Object.class); // $unsafeDeserialization } @GetMapping(value = "hessian2") @@ -69,14 +69,14 @@ public void bad5(HttpServletRequest request) throws Exception { byte[] bytes = request.getParameter("data").getBytes(); ByteArrayInputStream bis = new ByteArrayInputStream(bytes); Hessian2Input hessianInput = new Hessian2Input(bis); - hessianInput.readObject(); //bad - hessianInput.readObject(Object.class); //bad + hessianInput.readObject(); // $unsafeDeserialization + hessianInput.readObject(Object.class); // $unsafeDeserialization } @GetMapping(value = "castor") public void bad6(HttpServletRequest request) throws Exception { Unmarshaller unmarshaller = new Unmarshaller(); - unmarshaller.unmarshal(new StringReader(request.getParameter("data"))); //bad + unmarshaller.unmarshal(new StringReader(request.getParameter("data"))); // $unsafeDeserialization } @GetMapping(value = "burlap") @@ -84,11 +84,11 @@ public void bad7(HttpServletRequest request) throws Exception { byte[] serializedData = request.getParameter("data").getBytes(); ByteArrayInputStream is = new ByteArrayInputStream(serializedData); BurlapInput burlapInput = new BurlapInput(is); - burlapInput.readObject(); //bad + burlapInput.readObject(); // $unsafeDeserialization BurlapInput burlapInput1 = new BurlapInput(); burlapInput1.init(is); - burlapInput1.readObject(); //bad + burlapInput1.readObject(); // $unsafeDeserialization } @GetMapping(value = "jsonio1") diff --git a/java/ql/test/query-tests/security/CWE-502/JacksonTest.java b/java/ql/test/query-tests/security/CWE-502/JacksonTest.java new file mode 100644 index 000000000000..3520e4eaa116 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-502/JacksonTest.java @@ -0,0 +1,209 @@ +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import java.io.IOException; +import java.io.Serializable; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.List; + +public class JacksonTest { + + public static void withSocket(Action action) throws Exception { + try (ServerSocket serverSocket = new ServerSocket(0)) { + try (Socket socket = serverSocket.accept()) { + byte[] bytes = new byte[1024]; + int n = socket.getInputStream().read(bytes); + String jexlExpr = new String(bytes, 0, n); + action.run(jexlExpr); + } + } + } +} + +interface Action { + void run(T object) throws Exception; +} + +abstract class PhoneNumber implements Serializable { + public int areaCode; + public int local; +} + +class DomesticNumber extends PhoneNumber { +} + +class InternationalNumber extends PhoneNumber { + public int countryCode; +} + +class Employee extends Person { +} + +class Person { + public String name; + public int age; + + // this annotation enables polymorphic type handling + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) + public Object phone; +} + +class Task { + public Person assignee; +} + +class Tag implements Serializable { + public String title; +} + +class Cat { + public String name; + public Serializable tag; +} + +class UnsafePersonDeserialization { + + // BAD: Person has a field with an annotation that enables polymorphic type + // handling + private static void testUnsafeDeserialization() throws Exception { + JacksonTest.withSocket(string -> { + ObjectMapper mapper = new ObjectMapper(); + mapper.readValue(string, Person.class); // $unsafeDeserialization + }); + } + + // BAD: Employee extends Person that has a field with an annotation that enables + // polymorphic type handling + private static void testUnsafeDeserializationWithExtendedClass() throws Exception { + JacksonTest.withSocket(string -> { + ObjectMapper mapper = new ObjectMapper(); + mapper.readValue(string, Employee.class); // $unsafeDeserialization + }); + } + + // BAD: Task has a Person field that has a field with an annotation that enables + // polymorphic type handling + private static void testUnsafeDeserializationWithWrapper() throws Exception { + JacksonTest.withSocket(string -> { + ObjectMapper mapper = new ObjectMapper(); + mapper.readValue(string, Task.class); // $unsafeDeserialization + }); + } +} + +class SaferPersonDeserialization { + + // GOOD: Despite enabled polymorphic type handling, this is safe because ObjectMapper + // has a validator + private static void testSafeDeserializationWithValidator() throws Exception { + JacksonTest.withSocket(string -> { + PolymorphicTypeValidator ptv = + BasicPolymorphicTypeValidator.builder() + .allowIfSubType("only.allowed.package") + .build(); + + ObjectMapper mapper = new ObjectMapper(); + mapper.setPolymorphicTypeValidator(ptv); + + mapper.readValue(string, Person.class); + }); + } + + // GOOD: Despite enabled polymorphic type handling, this is safe because ObjectMapper + // has a validator + private static void testSafeDeserializationWithValidatorAndBuilder() throws Exception { + JacksonTest.withSocket(string -> { + PolymorphicTypeValidator ptv = + BasicPolymorphicTypeValidator.builder() + .allowIfSubType("only.allowed.package") + .build(); + + ObjectMapper mapper = JsonMapper.builder() + .polymorphicTypeValidator(ptv) + .build(); + + mapper.readValue(string, Person.class); + }); + } +} + +class UnsafeCatDeserialization { + + // BAD: deserializing untrusted input while polymorphic type handling is on + private static void testUnsafeDeserialization() throws Exception { + JacksonTest.withSocket(string -> { + ObjectMapper mapper = new ObjectMapper(); + mapper.enableDefaultTyping(); // this enables polymorphic type handling + mapper.readValue(string, Cat.class); // $unsafeDeserialization + }); + } + + // BAD: deserializing untrusted input while polymorphic type handling is on + private static void testUnsafeDeserializationWithObjectMapperReadValues() throws Exception { + JacksonTest.withSocket(string -> { + ObjectMapper mapper = new ObjectMapper(); + mapper.enableDefaultTyping(); + mapper.readValues(new JsonFactory().createParser(string), Cat.class).readAll(); // $unsafeDeserialization + }); + } + + // BAD: deserializing untrusted input while polymorphic type handling is on + private static void testUnsafeDeserializationWithObjectMapperTreeToValue() throws Exception { + JacksonTest.withSocket(string -> { + ObjectMapper mapper = new ObjectMapper(); + mapper.enableDefaultTyping(); + mapper.treeToValue(mapper.readTree(string), Cat.class); // $unsafeDeserialization + }); + } + + // BAD: an attacker can control both data and type of deserialized object + private static void testUnsafeDeserializationWithUnsafeClass() throws Exception { + JacksonTest.withSocket(input -> { + String[] parts = input.split(";"); + String data = parts[0]; + String type = parts[1]; + Class clazz = Class.forName(type); + ObjectMapper mapper = new ObjectMapper(); + mapper.readValue(data, clazz); // $unsafeDeserialization + }); + } + + // BAD: an attacker can control both data and type of deserialized object + private static void testUnsafeDeserializationWithUnsafeClassAndCustomTypeResolver() throws Exception { + JacksonTest.withSocket(input -> { + String[] parts = input.split(";"); + String data = parts[0]; + String type = parts[1]; + ObjectMapper mapper = new ObjectMapper(); + mapper.readValue(data, resolveImpl(type, mapper)); // $unsafeDeserialization + }); + } + + private static JavaType resolveImpl(String type, ObjectMapper mapper) throws Exception { + return mapper.constructType(Class.forName(type)); + } +} + +class SaferCatDeserialization { + + // GOOD: Despite enabled polymorphic type handling, this is safe because ObjectMapper + // has a validator + private static void testUnsafeDeserialization() throws Exception { + JacksonTest.withSocket(string -> { + PolymorphicTypeValidator ptv = + BasicPolymorphicTypeValidator.builder() + .allowIfSubType("only.allowed.pachage") + .build(); + + ObjectMapper mapper = JsonMapper.builder().polymorphicTypeValidator(ptv).build(); + mapper.enableDefaultTyping(); // this enables polymorphic type handling + + mapper.readValue(string, Cat.class); + }); + } +} \ No newline at end of file diff --git a/java/ql/test/query-tests/security/CWE-502/TestMessageBodyReader.java b/java/ql/test/query-tests/security/CWE-502/TestMessageBodyReader.java index 7940b9c77010..2132041254da 100644 --- a/java/ql/test/query-tests/security/CWE-502/TestMessageBodyReader.java +++ b/java/ql/test/query-tests/security/CWE-502/TestMessageBodyReader.java @@ -19,7 +19,7 @@ public boolean isReadable(Class type, Type genericType, Annotation[] annotati public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException { try { - return new ObjectInputStream(entityStream).readObject(); + return new ObjectInputStream(entityStream).readObject(); // $unsafeDeserialization } catch (ClassNotFoundException e) { e.printStackTrace(); } diff --git a/java/ql/test/query-tests/security/CWE-502/UnsafeDeserialization.expected b/java/ql/test/query-tests/security/CWE-502/UnsafeDeserialization.expected index 7b02131cd734..e69de29bb2d1 100644 --- a/java/ql/test/query-tests/security/CWE-502/UnsafeDeserialization.expected +++ b/java/ql/test/query-tests/security/CWE-502/UnsafeDeserialization.expected @@ -1,229 +0,0 @@ -edges -| A.java:13:31:13:51 | getInputStream(...) : InputStream | A.java:14:50:14:60 | inputStream : InputStream | -| A.java:13:31:13:51 | getInputStream(...) : InputStream | A.java:15:12:15:13 | in | -| A.java:14:28:14:61 | new ObjectInputStream(...) : ObjectInputStream | A.java:15:12:15:13 | in | -| A.java:14:50:14:60 | inputStream : InputStream | A.java:14:28:14:61 | new ObjectInputStream(...) : ObjectInputStream | -| A.java:19:31:19:51 | getInputStream(...) : InputStream | A.java:20:50:20:60 | inputStream : InputStream | -| A.java:19:31:19:51 | getInputStream(...) : InputStream | A.java:21:12:21:13 | in | -| A.java:20:28:20:61 | new ObjectInputStream(...) : ObjectInputStream | A.java:21:12:21:13 | in | -| A.java:20:50:20:60 | inputStream : InputStream | A.java:20:28:20:61 | new ObjectInputStream(...) : ObjectInputStream | -| A.java:25:31:25:51 | getInputStream(...) : InputStream | A.java:26:35:26:45 | inputStream : InputStream | -| A.java:26:20:26:46 | new XMLDecoder(...) : XMLDecoder | A.java:27:12:27:12 | d | -| A.java:26:35:26:45 | inputStream : InputStream | A.java:26:20:26:46 | new XMLDecoder(...) : XMLDecoder | -| A.java:32:31:32:51 | getInputStream(...) : InputStream | A.java:33:43:33:53 | inputStream : InputStream | -| A.java:33:21:33:54 | new InputStreamReader(...) : InputStreamReader | A.java:34:23:34:28 | reader | -| A.java:33:43:33:53 | inputStream : InputStream | A.java:33:21:33:54 | new InputStreamReader(...) : InputStreamReader | -| A.java:39:19:39:50 | new Input(...) : Input | A.java:40:28:40:32 | input | -| A.java:39:19:39:50 | new Input(...) : Input | A.java:41:34:41:38 | input | -| A.java:39:19:39:50 | new Input(...) : Input | A.java:42:40:42:44 | input | -| A.java:39:29:39:49 | getInputStream(...) : InputStream | A.java:39:19:39:50 | new Input(...) : Input | -| A.java:60:25:60:45 | getInputStream(...) : InputStream | A.java:61:26:61:30 | input | -| A.java:60:25:60:45 | getInputStream(...) : InputStream | A.java:62:30:62:34 | input | -| A.java:60:25:60:45 | getInputStream(...) : InputStream | A.java:63:50:63:54 | input : InputStream | -| A.java:60:25:60:45 | getInputStream(...) : InputStream | A.java:64:24:64:28 | input | -| A.java:60:25:60:45 | getInputStream(...) : InputStream | A.java:65:46:65:50 | input : InputStream | -| A.java:63:50:63:54 | input : InputStream | A.java:63:28:63:55 | new InputStreamReader(...) | -| A.java:65:46:65:50 | input : InputStream | A.java:65:24:65:51 | new InputStreamReader(...) | -| A.java:70:25:70:45 | getInputStream(...) : InputStream | A.java:71:26:71:30 | input | -| A.java:70:25:70:45 | getInputStream(...) : InputStream | A.java:72:30:72:34 | input | -| A.java:70:25:70:45 | getInputStream(...) : InputStream | A.java:73:50:73:54 | input : InputStream | -| A.java:70:25:70:45 | getInputStream(...) : InputStream | A.java:74:24:74:28 | input | -| A.java:70:25:70:45 | getInputStream(...) : InputStream | A.java:75:46:75:50 | input : InputStream | -| A.java:73:50:73:54 | input : InputStream | A.java:73:28:73:55 | new InputStreamReader(...) | -| A.java:75:46:75:50 | input : InputStream | A.java:75:24:75:51 | new InputStreamReader(...) | -| A.java:90:25:90:45 | getInputStream(...) : InputStream | A.java:91:26:91:30 | input | -| A.java:90:25:90:45 | getInputStream(...) : InputStream | A.java:92:30:92:34 | input | -| A.java:90:25:90:45 | getInputStream(...) : InputStream | A.java:93:50:93:54 | input : InputStream | -| A.java:90:25:90:45 | getInputStream(...) : InputStream | A.java:94:24:94:28 | input | -| A.java:90:25:90:45 | getInputStream(...) : InputStream | A.java:95:46:95:50 | input : InputStream | -| A.java:93:50:93:54 | input : InputStream | A.java:93:28:93:55 | new InputStreamReader(...) | -| A.java:95:46:95:50 | input : InputStream | A.java:95:24:95:51 | new InputStreamReader(...) | -| B.java:7:31:7:51 | getInputStream(...) : InputStream | B.java:8:29:8:39 | inputStream | -| B.java:12:31:12:51 | getInputStream(...) : InputStream | B.java:14:5:14:15 | inputStream : InputStream | -| B.java:14:5:14:15 | inputStream : InputStream | B.java:14:22:14:26 | bytes [post update] : byte[] | -| B.java:14:22:14:26 | bytes [post update] : byte[] | B.java:15:23:15:27 | bytes | -| B.java:19:31:19:51 | getInputStream(...) : InputStream | B.java:21:5:21:15 | inputStream : InputStream | -| B.java:21:5:21:15 | inputStream : InputStream | B.java:21:22:21:26 | bytes [post update] : byte[] | -| B.java:21:22:21:26 | bytes [post update] : byte[] | B.java:23:29:23:29 | s | -| B.java:27:31:27:51 | getInputStream(...) : InputStream | B.java:29:5:29:15 | inputStream : InputStream | -| B.java:29:5:29:15 | inputStream : InputStream | B.java:29:22:29:26 | bytes [post update] : byte[] | -| B.java:29:22:29:26 | bytes [post update] : byte[] | B.java:31:23:31:23 | s | -| C.java:23:17:23:44 | getParameter(...) : String | C.java:24:13:24:16 | data | -| C.java:23:17:23:44 | getParameter(...) : String | C.java:25:19:25:22 | data | -| C.java:23:17:23:44 | getParameter(...) : String | C.java:26:25:26:28 | data | -| C.java:23:17:23:44 | getParameter(...) : String | C.java:27:17:27:20 | data | -| C.java:23:17:23:44 | getParameter(...) : String | C.java:30:19:30:22 | data | -| C.java:23:17:23:44 | getParameter(...) : String | C.java:31:25:31:28 | data | -| C.java:23:17:23:44 | getParameter(...) : String | C.java:32:31:32:34 | data | -| C.java:23:17:23:44 | getParameter(...) : String | C.java:33:23:33:26 | data | -| C.java:38:17:38:44 | getParameter(...) : String | C.java:43:25:43:28 | data | -| C.java:38:17:38:44 | getParameter(...) : String | C.java:46:3:46:4 | jr | -| C.java:51:17:51:44 | getParameter(...) : String | C.java:53:3:53:3 | r | -| C.java:51:17:51:44 | getParameter(...) : String | C.java:54:3:54:3 | r | -| C.java:51:17:51:44 | getParameter(...) : String | C.java:55:3:55:3 | r | -| C.java:60:18:60:45 | getParameter(...) : String | C.java:61:55:61:59 | bytes : byte[] | -| C.java:60:18:60:45 | getParameter(...) : String | C.java:63:3:63:14 | hessianInput | -| C.java:60:18:60:45 | getParameter(...) : String | C.java:64:3:64:14 | hessianInput | -| C.java:61:30:61:60 | new ByteArrayInputStream(...) : ByteArrayInputStream | C.java:63:3:63:14 | hessianInput | -| C.java:61:30:61:60 | new ByteArrayInputStream(...) : ByteArrayInputStream | C.java:64:3:64:14 | hessianInput | -| C.java:61:55:61:59 | bytes : byte[] | C.java:61:30:61:60 | new ByteArrayInputStream(...) : ByteArrayInputStream | -| C.java:69:18:69:45 | getParameter(...) : String | C.java:70:55:70:59 | bytes : byte[] | -| C.java:69:18:69:45 | getParameter(...) : String | C.java:72:3:72:14 | hessianInput | -| C.java:69:18:69:45 | getParameter(...) : String | C.java:73:3:73:14 | hessianInput | -| C.java:70:30:70:60 | new ByteArrayInputStream(...) : ByteArrayInputStream | C.java:72:3:72:14 | hessianInput | -| C.java:70:30:70:60 | new ByteArrayInputStream(...) : ByteArrayInputStream | C.java:73:3:73:14 | hessianInput | -| C.java:70:55:70:59 | bytes : byte[] | C.java:70:30:70:60 | new ByteArrayInputStream(...) : ByteArrayInputStream | -| C.java:79:43:79:70 | getParameter(...) : String | C.java:79:26:79:71 | new StringReader(...) | -| C.java:84:27:84:54 | getParameter(...) : String | C.java:85:54:85:67 | serializedData : byte[] | -| C.java:84:27:84:54 | getParameter(...) : String | C.java:87:3:87:13 | burlapInput | -| C.java:84:27:84:54 | getParameter(...) : String | C.java:91:3:91:14 | burlapInput1 | -| C.java:85:29:85:68 | new ByteArrayInputStream(...) : ByteArrayInputStream | C.java:87:3:87:13 | burlapInput | -| C.java:85:29:85:68 | new ByteArrayInputStream(...) : ByteArrayInputStream | C.java:91:3:91:14 | burlapInput1 | -| C.java:85:54:85:67 | serializedData : byte[] | C.java:85:29:85:68 | new ByteArrayInputStream(...) : ByteArrayInputStream | -| TestMessageBodyReader.java:20:55:20:78 | entityStream : InputStream | TestMessageBodyReader.java:22:18:22:52 | new ObjectInputStream(...) | -| TestMessageBodyReader.java:20:55:20:78 | entityStream : InputStream | TestMessageBodyReader.java:22:40:22:51 | entityStream : InputStream | -| TestMessageBodyReader.java:22:40:22:51 | entityStream : InputStream | TestMessageBodyReader.java:22:18:22:52 | new ObjectInputStream(...) | -nodes -| A.java:13:31:13:51 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| A.java:14:28:14:61 | new ObjectInputStream(...) : ObjectInputStream | semmle.label | new ObjectInputStream(...) : ObjectInputStream | -| A.java:14:50:14:60 | inputStream : InputStream | semmle.label | inputStream : InputStream | -| A.java:15:12:15:13 | in | semmle.label | in | -| A.java:19:31:19:51 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| A.java:20:28:20:61 | new ObjectInputStream(...) : ObjectInputStream | semmle.label | new ObjectInputStream(...) : ObjectInputStream | -| A.java:20:50:20:60 | inputStream : InputStream | semmle.label | inputStream : InputStream | -| A.java:21:12:21:13 | in | semmle.label | in | -| A.java:25:31:25:51 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| A.java:26:20:26:46 | new XMLDecoder(...) : XMLDecoder | semmle.label | new XMLDecoder(...) : XMLDecoder | -| A.java:26:35:26:45 | inputStream : InputStream | semmle.label | inputStream : InputStream | -| A.java:27:12:27:12 | d | semmle.label | d | -| A.java:32:31:32:51 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| A.java:33:21:33:54 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader | -| A.java:33:43:33:53 | inputStream : InputStream | semmle.label | inputStream : InputStream | -| A.java:34:23:34:28 | reader | semmle.label | reader | -| A.java:39:19:39:50 | new Input(...) : Input | semmle.label | new Input(...) : Input | -| A.java:39:29:39:49 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| A.java:40:28:40:32 | input | semmle.label | input | -| A.java:41:34:41:38 | input | semmle.label | input | -| A.java:42:40:42:44 | input | semmle.label | input | -| A.java:60:25:60:45 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| A.java:61:26:61:30 | input | semmle.label | input | -| A.java:62:30:62:34 | input | semmle.label | input | -| A.java:63:28:63:55 | new InputStreamReader(...) | semmle.label | new InputStreamReader(...) | -| A.java:63:50:63:54 | input : InputStream | semmle.label | input : InputStream | -| A.java:64:24:64:28 | input | semmle.label | input | -| A.java:65:24:65:51 | new InputStreamReader(...) | semmle.label | new InputStreamReader(...) | -| A.java:65:46:65:50 | input : InputStream | semmle.label | input : InputStream | -| A.java:70:25:70:45 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| A.java:71:26:71:30 | input | semmle.label | input | -| A.java:72:30:72:34 | input | semmle.label | input | -| A.java:73:28:73:55 | new InputStreamReader(...) | semmle.label | new InputStreamReader(...) | -| A.java:73:50:73:54 | input : InputStream | semmle.label | input : InputStream | -| A.java:74:24:74:28 | input | semmle.label | input | -| A.java:75:24:75:51 | new InputStreamReader(...) | semmle.label | new InputStreamReader(...) | -| A.java:75:46:75:50 | input : InputStream | semmle.label | input : InputStream | -| A.java:90:25:90:45 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| A.java:91:26:91:30 | input | semmle.label | input | -| A.java:92:30:92:34 | input | semmle.label | input | -| A.java:93:28:93:55 | new InputStreamReader(...) | semmle.label | new InputStreamReader(...) | -| A.java:93:50:93:54 | input : InputStream | semmle.label | input : InputStream | -| A.java:94:24:94:28 | input | semmle.label | input | -| A.java:95:24:95:51 | new InputStreamReader(...) | semmle.label | new InputStreamReader(...) | -| A.java:95:46:95:50 | input : InputStream | semmle.label | input : InputStream | -| B.java:7:31:7:51 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| B.java:8:29:8:39 | inputStream | semmle.label | inputStream | -| B.java:12:31:12:51 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| B.java:14:5:14:15 | inputStream : InputStream | semmle.label | inputStream : InputStream | -| B.java:14:22:14:26 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] | -| B.java:15:23:15:27 | bytes | semmle.label | bytes | -| B.java:19:31:19:51 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| B.java:21:5:21:15 | inputStream : InputStream | semmle.label | inputStream : InputStream | -| B.java:21:22:21:26 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] | -| B.java:23:29:23:29 | s | semmle.label | s | -| B.java:27:31:27:51 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| B.java:29:5:29:15 | inputStream : InputStream | semmle.label | inputStream : InputStream | -| B.java:29:22:29:26 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] | -| B.java:31:23:31:23 | s | semmle.label | s | -| C.java:23:17:23:44 | getParameter(...) : String | semmle.label | getParameter(...) : String | -| C.java:24:13:24:16 | data | semmle.label | data | -| C.java:25:19:25:22 | data | semmle.label | data | -| C.java:26:25:26:28 | data | semmle.label | data | -| C.java:27:17:27:20 | data | semmle.label | data | -| C.java:30:19:30:22 | data | semmle.label | data | -| C.java:31:25:31:28 | data | semmle.label | data | -| C.java:32:31:32:34 | data | semmle.label | data | -| C.java:33:23:33:26 | data | semmle.label | data | -| C.java:38:17:38:44 | getParameter(...) : String | semmle.label | getParameter(...) : String | -| C.java:43:25:43:28 | data | semmle.label | data | -| C.java:46:3:46:4 | jr | semmle.label | jr | -| C.java:51:17:51:44 | getParameter(...) : String | semmle.label | getParameter(...) : String | -| C.java:53:3:53:3 | r | semmle.label | r | -| C.java:54:3:54:3 | r | semmle.label | r | -| C.java:55:3:55:3 | r | semmle.label | r | -| C.java:60:18:60:45 | getParameter(...) : String | semmle.label | getParameter(...) : String | -| C.java:61:30:61:60 | new ByteArrayInputStream(...) : ByteArrayInputStream | semmle.label | new ByteArrayInputStream(...) : ByteArrayInputStream | -| C.java:61:55:61:59 | bytes : byte[] | semmle.label | bytes : byte[] | -| C.java:63:3:63:14 | hessianInput | semmle.label | hessianInput | -| C.java:64:3:64:14 | hessianInput | semmle.label | hessianInput | -| C.java:69:18:69:45 | getParameter(...) : String | semmle.label | getParameter(...) : String | -| C.java:70:30:70:60 | new ByteArrayInputStream(...) : ByteArrayInputStream | semmle.label | new ByteArrayInputStream(...) : ByteArrayInputStream | -| C.java:70:55:70:59 | bytes : byte[] | semmle.label | bytes : byte[] | -| C.java:72:3:72:14 | hessianInput | semmle.label | hessianInput | -| C.java:73:3:73:14 | hessianInput | semmle.label | hessianInput | -| C.java:79:26:79:71 | new StringReader(...) | semmle.label | new StringReader(...) | -| C.java:79:43:79:70 | getParameter(...) : String | semmle.label | getParameter(...) : String | -| C.java:84:27:84:54 | getParameter(...) : String | semmle.label | getParameter(...) : String | -| C.java:85:29:85:68 | new ByteArrayInputStream(...) : ByteArrayInputStream | semmle.label | new ByteArrayInputStream(...) : ByteArrayInputStream | -| C.java:85:54:85:67 | serializedData : byte[] | semmle.label | serializedData : byte[] | -| C.java:87:3:87:13 | burlapInput | semmle.label | burlapInput | -| C.java:91:3:91:14 | burlapInput1 | semmle.label | burlapInput1 | -| TestMessageBodyReader.java:20:55:20:78 | entityStream : InputStream | semmle.label | entityStream : InputStream | -| TestMessageBodyReader.java:22:18:22:52 | new ObjectInputStream(...) | semmle.label | new ObjectInputStream(...) | -| TestMessageBodyReader.java:22:40:22:51 | entityStream : InputStream | semmle.label | entityStream : InputStream | -#select -| A.java:15:12:15:26 | readObject(...) | A.java:13:31:13:51 | getInputStream(...) : InputStream | A.java:15:12:15:13 | in | Unsafe deserialization of $@. | A.java:13:31:13:51 | getInputStream(...) | user input | -| A.java:21:12:21:28 | readUnshared(...) | A.java:19:31:19:51 | getInputStream(...) : InputStream | A.java:21:12:21:13 | in | Unsafe deserialization of $@. | A.java:19:31:19:51 | getInputStream(...) | user input | -| A.java:27:12:27:25 | readObject(...) | A.java:25:31:25:51 | getInputStream(...) : InputStream | A.java:27:12:27:12 | d | Unsafe deserialization of $@. | A.java:25:31:25:51 | getInputStream(...) | user input | -| A.java:34:12:34:29 | fromXML(...) | A.java:32:31:32:51 | getInputStream(...) : InputStream | A.java:34:23:34:28 | reader | Unsafe deserialization of $@. | A.java:32:31:32:51 | getInputStream(...) | user input | -| A.java:40:12:40:42 | readObject(...) | A.java:39:29:39:49 | getInputStream(...) : InputStream | A.java:40:28:40:32 | input | Unsafe deserialization of $@. | A.java:39:29:39:49 | getInputStream(...) | user input | -| A.java:41:12:41:48 | readObjectOrNull(...) | A.java:39:29:39:49 | getInputStream(...) : InputStream | A.java:41:34:41:38 | input | Unsafe deserialization of $@. | A.java:39:29:39:49 | getInputStream(...) | user input | -| A.java:42:16:42:45 | readClassAndObject(...) | A.java:39:29:39:49 | getInputStream(...) : InputStream | A.java:42:40:42:44 | input | Unsafe deserialization of $@. | A.java:39:29:39:49 | getInputStream(...) | user input | -| A.java:61:16:61:31 | load(...) | A.java:60:25:60:45 | getInputStream(...) : InputStream | A.java:61:26:61:30 | input | Unsafe deserialization of $@. | A.java:60:25:60:45 | getInputStream(...) | user input | -| A.java:62:17:62:35 | loadAll(...) | A.java:60:25:60:45 | getInputStream(...) : InputStream | A.java:62:30:62:34 | input | Unsafe deserialization of $@. | A.java:60:25:60:45 | getInputStream(...) | user input | -| A.java:63:17:63:56 | parse(...) | A.java:60:25:60:45 | getInputStream(...) : InputStream | A.java:63:28:63:55 | new InputStreamReader(...) | Unsafe deserialization of $@. | A.java:60:25:60:45 | getInputStream(...) | user input | -| A.java:64:12:64:38 | loadAs(...) | A.java:60:25:60:45 | getInputStream(...) : InputStream | A.java:64:24:64:28 | input | Unsafe deserialization of $@. | A.java:60:25:60:45 | getInputStream(...) | user input | -| A.java:65:12:65:61 | loadAs(...) | A.java:60:25:60:45 | getInputStream(...) : InputStream | A.java:65:24:65:51 | new InputStreamReader(...) | Unsafe deserialization of $@. | A.java:60:25:60:45 | getInputStream(...) | user input | -| A.java:71:16:71:31 | load(...) | A.java:70:25:70:45 | getInputStream(...) : InputStream | A.java:71:26:71:30 | input | Unsafe deserialization of $@. | A.java:70:25:70:45 | getInputStream(...) | user input | -| A.java:72:17:72:35 | loadAll(...) | A.java:70:25:70:45 | getInputStream(...) : InputStream | A.java:72:30:72:34 | input | Unsafe deserialization of $@. | A.java:70:25:70:45 | getInputStream(...) | user input | -| A.java:73:17:73:56 | parse(...) | A.java:70:25:70:45 | getInputStream(...) : InputStream | A.java:73:28:73:55 | new InputStreamReader(...) | Unsafe deserialization of $@. | A.java:70:25:70:45 | getInputStream(...) | user input | -| A.java:74:12:74:38 | loadAs(...) | A.java:70:25:70:45 | getInputStream(...) : InputStream | A.java:74:24:74:28 | input | Unsafe deserialization of $@. | A.java:70:25:70:45 | getInputStream(...) | user input | -| A.java:75:12:75:61 | loadAs(...) | A.java:70:25:70:45 | getInputStream(...) : InputStream | A.java:75:24:75:51 | new InputStreamReader(...) | Unsafe deserialization of $@. | A.java:70:25:70:45 | getInputStream(...) | user input | -| A.java:91:16:91:31 | load(...) | A.java:90:25:90:45 | getInputStream(...) : InputStream | A.java:91:26:91:30 | input | Unsafe deserialization of $@. | A.java:90:25:90:45 | getInputStream(...) | user input | -| A.java:92:17:92:35 | loadAll(...) | A.java:90:25:90:45 | getInputStream(...) : InputStream | A.java:92:30:92:34 | input | Unsafe deserialization of $@. | A.java:90:25:90:45 | getInputStream(...) | user input | -| A.java:93:17:93:56 | parse(...) | A.java:90:25:90:45 | getInputStream(...) : InputStream | A.java:93:28:93:55 | new InputStreamReader(...) | Unsafe deserialization of $@. | A.java:90:25:90:45 | getInputStream(...) | user input | -| A.java:94:12:94:38 | loadAs(...) | A.java:90:25:90:45 | getInputStream(...) : InputStream | A.java:94:24:94:28 | input | Unsafe deserialization of $@. | A.java:90:25:90:45 | getInputStream(...) | user input | -| A.java:95:12:95:61 | loadAs(...) | A.java:90:25:90:45 | getInputStream(...) : InputStream | A.java:95:24:95:51 | new InputStreamReader(...) | Unsafe deserialization of $@. | A.java:90:25:90:45 | getInputStream(...) | user input | -| B.java:8:12:8:46 | parseObject(...) | B.java:7:31:7:51 | getInputStream(...) : InputStream | B.java:8:29:8:39 | inputStream | Unsafe deserialization of $@. | B.java:7:31:7:51 | getInputStream(...) | user input | -| B.java:15:12:15:28 | parse(...) | B.java:12:31:12:51 | getInputStream(...) : InputStream | B.java:15:23:15:27 | bytes | Unsafe deserialization of $@. | B.java:12:31:12:51 | getInputStream(...) | user input | -| B.java:23:12:23:30 | parseObject(...) | B.java:19:31:19:51 | getInputStream(...) : InputStream | B.java:23:29:23:29 | s | Unsafe deserialization of $@. | B.java:19:31:19:51 | getInputStream(...) | user input | -| B.java:31:12:31:24 | parse(...) | B.java:27:31:27:51 | getInputStream(...) : InputStream | B.java:31:23:31:23 | s | Unsafe deserialization of $@. | B.java:27:31:27:51 | getInputStream(...) | user input | -| C.java:24:3:24:17 | load(...) | C.java:23:17:23:44 | getParameter(...) : String | C.java:24:13:24:16 | data | Unsafe deserialization of $@. | C.java:23:17:23:44 | getParameter(...) | user input | -| C.java:25:3:25:23 | loadStream(...) | C.java:23:17:23:44 | getParameter(...) : String | C.java:25:19:25:22 | data | Unsafe deserialization of $@. | C.java:23:17:23:44 | getParameter(...) | user input | -| C.java:26:3:26:43 | loadStreamOfType(...) | C.java:23:17:23:44 | getParameter(...) : String | C.java:26:25:26:28 | data | Unsafe deserialization of $@. | C.java:23:17:23:44 | getParameter(...) | user input | -| C.java:27:3:27:35 | loadType(...) | C.java:23:17:23:44 | getParameter(...) : String | C.java:27:17:27:20 | data | Unsafe deserialization of $@. | C.java:23:17:23:44 | getParameter(...) | user input | -| C.java:30:3:30:23 | load(...) | C.java:23:17:23:44 | getParameter(...) : String | C.java:30:19:30:22 | data | Unsafe deserialization of $@. | C.java:23:17:23:44 | getParameter(...) | user input | -| C.java:31:3:31:29 | loadStream(...) | C.java:23:17:23:44 | getParameter(...) : String | C.java:31:25:31:28 | data | Unsafe deserialization of $@. | C.java:23:17:23:44 | getParameter(...) | user input | -| C.java:32:3:32:49 | loadStreamOfType(...) | C.java:23:17:23:44 | getParameter(...) : String | C.java:32:31:32:34 | data | Unsafe deserialization of $@. | C.java:23:17:23:44 | getParameter(...) | user input | -| C.java:33:3:33:41 | loadType(...) | C.java:23:17:23:44 | getParameter(...) : String | C.java:33:23:33:26 | data | Unsafe deserialization of $@. | C.java:23:17:23:44 | getParameter(...) | user input | -| C.java:43:3:43:29 | jsonToJava(...) | C.java:38:17:38:44 | getParameter(...) : String | C.java:43:25:43:28 | data | Unsafe deserialization of $@. | C.java:38:17:38:44 | getParameter(...) | user input | -| C.java:46:3:46:17 | readObject(...) | C.java:38:17:38:44 | getParameter(...) : String | C.java:46:3:46:4 | jr | Unsafe deserialization of $@. | C.java:38:17:38:44 | getParameter(...) | user input | -| C.java:53:3:53:10 | read(...) | C.java:51:17:51:44 | getParameter(...) : String | C.java:53:3:53:3 | r | Unsafe deserialization of $@. | C.java:51:17:51:44 | getParameter(...) | user input | -| C.java:54:3:54:22 | read(...) | C.java:51:17:51:44 | getParameter(...) : String | C.java:54:3:54:3 | r | Unsafe deserialization of $@. | C.java:51:17:51:44 | getParameter(...) | user input | -| C.java:55:3:55:36 | read(...) | C.java:51:17:51:44 | getParameter(...) : String | C.java:55:3:55:3 | r | Unsafe deserialization of $@. | C.java:51:17:51:44 | getParameter(...) | user input | -| C.java:63:3:63:27 | readObject(...) | C.java:60:18:60:45 | getParameter(...) : String | C.java:63:3:63:14 | hessianInput | Unsafe deserialization of $@. | C.java:60:18:60:45 | getParameter(...) | user input | -| C.java:64:3:64:39 | readObject(...) | C.java:60:18:60:45 | getParameter(...) : String | C.java:64:3:64:14 | hessianInput | Unsafe deserialization of $@. | C.java:60:18:60:45 | getParameter(...) | user input | -| C.java:72:3:72:27 | readObject(...) | C.java:69:18:69:45 | getParameter(...) : String | C.java:72:3:72:14 | hessianInput | Unsafe deserialization of $@. | C.java:69:18:69:45 | getParameter(...) | user input | -| C.java:73:3:73:39 | readObject(...) | C.java:69:18:69:45 | getParameter(...) : String | C.java:73:3:73:14 | hessianInput | Unsafe deserialization of $@. | C.java:69:18:69:45 | getParameter(...) | user input | -| C.java:79:3:79:72 | unmarshal(...) | C.java:79:43:79:70 | getParameter(...) : String | C.java:79:26:79:71 | new StringReader(...) | Unsafe deserialization of $@. | C.java:79:43:79:70 | getParameter(...) | user input | -| C.java:87:3:87:26 | readObject(...) | C.java:84:27:84:54 | getParameter(...) : String | C.java:87:3:87:13 | burlapInput | Unsafe deserialization of $@. | C.java:84:27:84:54 | getParameter(...) | user input | -| C.java:91:3:91:27 | readObject(...) | C.java:84:27:84:54 | getParameter(...) : String | C.java:91:3:91:14 | burlapInput1 | Unsafe deserialization of $@. | C.java:84:27:84:54 | getParameter(...) | user input | -| TestMessageBodyReader.java:22:18:22:65 | readObject(...) | TestMessageBodyReader.java:20:55:20:78 | entityStream : InputStream | TestMessageBodyReader.java:22:18:22:52 | new ObjectInputStream(...) | Unsafe deserialization of $@. | TestMessageBodyReader.java:20:55:20:78 | entityStream | user input | diff --git a/java/ql/test/query-tests/security/CWE-502/UnsafeDeserialization.ql b/java/ql/test/query-tests/security/CWE-502/UnsafeDeserialization.ql new file mode 100644 index 000000000000..a2ba654a5409 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-502/UnsafeDeserialization.ql @@ -0,0 +1,18 @@ +import java +import semmle.code.java.security.UnsafeDeserializationQuery +import TestUtilities.InlineExpectationsTest + +class UnsafeDeserializationTest extends InlineExpectationsTest { + UnsafeDeserializationTest() { this = "UnsafeDeserializationTest" } + + override string getARelevantTag() { result = "unsafeDeserialization" } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + tag = "unsafeDeserialization" and + exists(DataFlow::Node sink, UnsafeDeserializationConfig conf | conf.hasFlowTo(sink) | + sink.getLocation() = location and + element = sink.toString() and + value = "" + ) + } +} diff --git a/java/ql/test/query-tests/security/CWE-502/UnsafeDeserialization.qlref b/java/ql/test/query-tests/security/CWE-502/UnsafeDeserialization.qlref deleted file mode 100644 index 30612dac5a58..000000000000 --- a/java/ql/test/query-tests/security/CWE-502/UnsafeDeserialization.qlref +++ /dev/null @@ -1 +0,0 @@ -Security/CWE/CWE-502/UnsafeDeserialization.ql diff --git a/java/ql/test/query-tests/security/CWE-502/options b/java/ql/test/query-tests/security/CWE-502/options index 03027487dce3..fc5cac9e843d 100644 --- a/java/ql/test/query-tests/security/CWE-502/options +++ b/java/ql/test/query-tests/security/CWE-502/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/snakeyaml-1.21:${testdir}/../../../stubs/xstream-1.4.10:${testdir}/../../../stubs/kryo-4.0.2:${testdir}/../../../stubs/jsr311-api-1.1.1:${testdir}/../../../stubs/fastjson-1.2.74:${testdir}/../../../stubs/springframework-5.3.8:${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/jyaml-1.3:${testdir}/../../../stubs/json-io-4.10.0:${testdir}/../../../stubs/yamlbeans-1.09:${testdir}/../../../stubs/hessian-4.0.38:${testdir}/../../../stubs/castor-1.4.1 +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/snakeyaml-1.21:${testdir}/../../../stubs/xstream-1.4.10:${testdir}/../../../stubs/kryo-4.0.2:${testdir}/../../../stubs/jsr311-api-1.1.1:${testdir}/../../../stubs/fastjson-1.2.74:${testdir}/../../../stubs/springframework-5.3.8:${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/jyaml-1.3:${testdir}/../../../stubs/json-io-4.10.0:${testdir}/../../../stubs/yamlbeans-1.09:${testdir}/../../../stubs/hessian-4.0.38:${testdir}/../../../stubs/castor-1.4.1:${testdir}/../../../stubs/jackson-databind-2.10 diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/annotation/JsonTypeInfo.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/annotation/JsonTypeInfo.java new file mode 100644 index 000000000000..fb6733f40192 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/annotation/JsonTypeInfo.java @@ -0,0 +1,21 @@ +package com.fasterxml.jackson.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface JsonTypeInfo { + JsonTypeInfo.Id use(); + + public static enum Id { + CLASS("@class"), + MINIMAL_CLASS("@c"); + + private Id(String defProp) { } + + public String getDefaultPropertyName() { return null; } + } +} diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/JsonFactory.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/JsonFactory.java index 06f71ab187d6..12696cd4397e 100644 --- a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/JsonFactory.java +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/JsonFactory.java @@ -9,4 +9,8 @@ public JsonFactory() { public JsonGenerator createGenerator(Writer writer) { return new JsonGenerator(); } + + public JsonParser createParser(String content) { + return null; + } } diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/JsonParser.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/JsonParser.java new file mode 100644 index 000000000000..2c5527d50ab7 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/JsonParser.java @@ -0,0 +1,3 @@ +package com.fasterxml.jackson.core; + +public abstract class JsonParser {} diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/TreeNode.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/TreeNode.java new file mode 100644 index 000000000000..0d89838457a6 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/TreeNode.java @@ -0,0 +1,3 @@ +package com.fasterxml.jackson.core; + +public interface TreeNode {} diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JavaType.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JavaType.java new file mode 100644 index 000000000000..51fe62ca4b15 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JavaType.java @@ -0,0 +1,3 @@ +package com.fasterxml.jackson.databind; + +public class JavaType {} \ No newline at end of file diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java index b04572cd4da1..06602e943f5a 100644 --- a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java @@ -1,8 +1,8 @@ package com.fasterxml.jackson.databind; import java.util.*; +import com.fasterxml.jackson.core.TreeNode; -public abstract class JsonNode implements Iterable { - public JsonNode() { - } +public abstract class JsonNode implements TreeNode, Iterable { + public JsonNode() {} } diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/MappingIterator.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/MappingIterator.java index ac427ef01c9d..929676e64568 100644 --- a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/MappingIterator.java +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/MappingIterator.java @@ -25,4 +25,8 @@ public void remove() { public void close() throws IOException { } + + public List readAll() { + return null; + } } diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java index 71dc99a351da..754fcc43144b 100644 --- a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java @@ -1,5 +1,9 @@ package com.fasterxml.jackson.databind; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import java.lang.reflect.Type; import java.io.*; import java.util.*; @@ -38,4 +42,36 @@ public T valueToTree(Object fromValue) throws IllegalArgume public T convertValue(Object fromValue, Class toValueType) throws IllegalArgumentException { return null; } + + public ObjectMapper setPolymorphicTypeValidator(PolymorphicTypeValidator ptv) { + return null; + } + + public ObjectMapper enableDefaultTyping() { + return null; + } + + public T readValue(String content, Class valueType) { + return null; + } + + public T readValue(String content, JavaType valueType) { + return null; + } + + public MappingIterator readValues(JsonParser p, Class valueType) { + return null; + } + + public T treeToValue(TreeNode n, Class valueType) { + return null; + } + + public JsonNode readTree(String content) { + return null; + } + + public JavaType constructType(Type t) { + return null; + } } diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/cfg/MapperBuilder.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/cfg/MapperBuilder.java new file mode 100644 index 000000000000..db2c24c43628 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/cfg/MapperBuilder.java @@ -0,0 +1,9 @@ +package com.fasterxml.jackson.databind.cfg; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; + +public abstract class MapperBuilder> { + public M build() { return null; } + public B polymorphicTypeValidator(PolymorphicTypeValidator ptv) { return null; } +} diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/json/JsonMapper.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/json/JsonMapper.java new file mode 100644 index 000000000000..adec92a9210a --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/json/JsonMapper.java @@ -0,0 +1,9 @@ +package com.fasterxml.jackson.databind.json; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.cfg.MapperBuilder; + +public class JsonMapper extends ObjectMapper { + public static JsonMapper.Builder builder() { return null; } + public static class Builder extends MapperBuilder {} +} \ No newline at end of file diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/jsontype/BasicPolymorphicTypeValidator.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/jsontype/BasicPolymorphicTypeValidator.java new file mode 100644 index 000000000000..243cd467b4b4 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/jsontype/BasicPolymorphicTypeValidator.java @@ -0,0 +1,10 @@ +package com.fasterxml.jackson.databind.jsontype; + +public class BasicPolymorphicTypeValidator extends PolymorphicTypeValidator { + public static BasicPolymorphicTypeValidator.Builder builder() { return null; } + + public static class Builder { + public BasicPolymorphicTypeValidator.Builder allowIfSubType(final String prefixForSubType) { return null; } + public BasicPolymorphicTypeValidator build() { return null; } + } +} diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/jsontype/PolymorphicTypeValidator.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/jsontype/PolymorphicTypeValidator.java new file mode 100644 index 000000000000..37e68d2c429f --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/jsontype/PolymorphicTypeValidator.java @@ -0,0 +1,3 @@ +package com.fasterxml.jackson.databind.jsontype; + +public abstract class PolymorphicTypeValidator {}