Skip to content

Commit 3b676d4

Browse files
authored
Merge pull request #5900 from artem-smotrakov/unsafe-jackson-deserialization
Java: Unsafe deserialization with Jackson
2 parents 0a1c754 + 6c973b5 commit 3b676d4

28 files changed

+892
-500
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
lgtm,codescanning
2+
* The "Deserialization of user-controlled data" (`java/unsafe-deserialization`) query
3+
now recognizes `Jackson` deserialization.

java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.qhelp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ may have unforeseen effects, such as the execution of arbitrary code.
1414
</p>
1515
<p>
1616
There are many different serialization frameworks. This query currently
17-
supports Kryo, XmlDecoder, XStream, SnakeYaml, JYaml, JsonIO, YAMLBeans, HessianBurlap, Castor, Burlap
18-
and Java IO serialization through <code>ObjectInputStream</code>/<code>ObjectOutputStream</code>.
17+
supports Kryo, XmlDecoder, XStream, SnakeYaml, JYaml, JsonIO, YAMLBeans, HessianBurlap, Castor, Burlap,
18+
Jackson and Java IO serialization through <code>ObjectInputStream</code>/<code>ObjectOutputStream</code>.
1919
</p>
2020
</overview>
2121

@@ -91,6 +91,15 @@ Remote code execution in JYaml library:
9191
JsonIO deserialization vulnerabilities:
9292
<a href="https://klezvirus.github.io/Advanced-Web-Hacking/Serialisation/">JsonIO deserialization</a>.
9393
</li>
94+
<li>
95+
Research by Moritz Bechler:
96+
<a href="https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true">Java Unmarshaller Security - Turning your data into code execution</a>
97+
</li>
98+
<li>
99+
Blog posts by the developer of Jackson libraries:
100+
<a href="https://cowtowncoder.medium.com/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062">On Jackson CVEs: Don’t Panic — Here is what you need to know</a>
101+
<a href="https://cowtowncoder.medium.com/jackson-2-10-safe-default-typing-2d018f0ce2ba">Jackson 2.10: Safe Default Typing</a>
102+
</li>
94103
</references>
95104

96105
</qhelp>

java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.ql

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12,51 +12,9 @@
1212
*/
1313

1414
import java
15-
import semmle.code.java.dataflow.FlowSources
16-
import semmle.code.java.security.UnsafeDeserialization
15+
import semmle.code.java.security.UnsafeDeserializationQuery
1716
import DataFlow::PathGraph
1817

19-
class UnsafeDeserializationConfig extends TaintTracking::Configuration {
20-
UnsafeDeserializationConfig() { this = "UnsafeDeserializationConfig" }
21-
22-
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
23-
24-
override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeDeserializationSink }
25-
26-
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
27-
exists(ClassInstanceExpr cie |
28-
cie.getArgument(0) = pred.asExpr() and
29-
cie = succ.asExpr() and
30-
(
31-
cie.getConstructor().getDeclaringType() instanceof JsonIoJsonReader or
32-
cie.getConstructor().getDeclaringType() instanceof YamlBeansReader or
33-
cie.getConstructor().getDeclaringType().getASupertype*() instanceof UnsafeHessianInput or
34-
cie.getConstructor().getDeclaringType() instanceof BurlapInput
35-
)
36-
)
37-
or
38-
exists(MethodAccess ma |
39-
ma.getMethod() instanceof BurlapInputInitMethod and
40-
ma.getArgument(0) = pred.asExpr() and
41-
ma.getQualifier() = succ.asExpr()
42-
)
43-
}
44-
45-
override predicate isSanitizer(DataFlow::Node node) {
46-
exists(ClassInstanceExpr cie |
47-
cie.getConstructor().getDeclaringType() instanceof JsonIoJsonReader and
48-
cie = node.asExpr() and
49-
exists(SafeJsonIoConfig sji | sji.hasFlowToExpr(cie.getArgument(1)))
50-
)
51-
or
52-
exists(MethodAccess ma |
53-
ma.getMethod() instanceof JsonIoJsonToJavaMethod and
54-
ma.getArgument(0) = node.asExpr() and
55-
exists(SafeJsonIoConfig sji | sji.hasFlowToExpr(ma.getArgument(1)))
56-
)
57-
}
58-
}
59-
6018
from DataFlow::PathNode source, DataFlow::PathNode sink, UnsafeDeserializationConfig conf
6119
where conf.hasFlowPath(source, sink)
6220
select sink.getNode().(UnsafeDeserializationSink).getMethodAccess(), source, sink,
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/**
2+
* Provides classes and predicates for working with the Jackson serialization framework.
3+
*/
4+
5+
import java
6+
private import semmle.code.java.Reflection
7+
private import semmle.code.java.dataflow.DataFlow
8+
9+
private class ObjectMapper extends RefType {
10+
ObjectMapper() {
11+
getASupertype*().hasQualifiedName("com.fasterxml.jackson.databind", "ObjectMapper")
12+
}
13+
}
14+
15+
/** A builder for building Jackson's `JsonMapper`. */
16+
class MapperBuilder extends RefType {
17+
MapperBuilder() {
18+
hasQualifiedName("com.fasterxml.jackson.databind.cfg", "MapperBuilder<JsonMapper,Builder>")
19+
}
20+
}
21+
22+
private class JsonFactory extends RefType {
23+
JsonFactory() { hasQualifiedName("com.fasterxml.jackson.core", "JsonFactory") }
24+
}
25+
26+
private class JsonParser extends RefType {
27+
JsonParser() { hasQualifiedName("com.fasterxml.jackson.core", "JsonParser") }
28+
}
29+
30+
/** A type descriptor in Jackson libraries. For example, `java.lang.Class`. */
31+
class JacksonTypeDescriptorType extends RefType {
32+
JacksonTypeDescriptorType() {
33+
this instanceof TypeClass or
34+
hasQualifiedName("com.fasterxml.jackson.databind", "JavaType") or
35+
hasQualifiedName("com.fasterxml.jackson.core.type", "TypeReference")
36+
}
37+
}
38+
39+
/** A method in `ObjectMapper` that deserialize data. */
40+
class ObjectMapperReadMethod extends Method {
41+
ObjectMapperReadMethod() {
42+
this.getDeclaringType() instanceof ObjectMapper and
43+
this.hasName(["readValue", "readValues", "treeToValue"])
44+
}
45+
}
46+
47+
/** A call that enables the default typing in `ObjectMapper`. */
48+
class EnableJacksonDefaultTyping extends MethodAccess {
49+
EnableJacksonDefaultTyping() {
50+
this.getMethod().getDeclaringType() instanceof ObjectMapper and
51+
this.getMethod().hasName("enableDefaultTyping")
52+
}
53+
}
54+
55+
/** A qualifier of a call to one of the methods in `ObjectMapper` that deserialize data. */
56+
class ObjectMapperReadQualifier extends DataFlow::ExprNode {
57+
ObjectMapperReadQualifier() {
58+
exists(MethodAccess ma | ma.getQualifier() = this.asExpr() |
59+
ma.getMethod() instanceof ObjectMapperReadMethod
60+
)
61+
}
62+
}
63+
64+
/** A source that sets a type validator. */
65+
class SetPolymorphicTypeValidatorSource extends DataFlow::ExprNode {
66+
SetPolymorphicTypeValidatorSource() {
67+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
68+
(
69+
m.getDeclaringType() instanceof ObjectMapper and
70+
m.hasName("setPolymorphicTypeValidator")
71+
or
72+
m.getDeclaringType() instanceof MapperBuilder and
73+
m.hasName("polymorphicTypeValidator")
74+
) and
75+
this.asExpr() = ma.getQualifier()
76+
)
77+
}
78+
}
79+
80+
/** Holds if `fromNode` to `toNode` is a dataflow step that resolves a class. */
81+
predicate resolveClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
82+
exists(ReflectiveClassIdentifierMethodAccess ma |
83+
ma.getArgument(0) = fromNode.asExpr() and
84+
ma = toNode.asExpr()
85+
)
86+
}
87+
88+
/**
89+
* Holds if `fromNode` to `toNode` is a dataflow step that creates a Jackson parser.
90+
*
91+
* For example, a `createParser(userString)` call yields a `JsonParser`, which becomes dangerous
92+
* if passed to an unsafely-configured `ObjectMapper`'s `readValue` method.
93+
*/
94+
predicate createJacksonJsonParserStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
95+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
96+
(m.getDeclaringType() instanceof ObjectMapper or m.getDeclaringType() instanceof JsonFactory) and
97+
m.hasName("createParser") and
98+
ma.getArgument(0) = fromNode.asExpr() and
99+
ma = toNode.asExpr()
100+
)
101+
}
102+
103+
/**
104+
* Holds if `fromNode` to `toNode` is a dataflow step that creates a Jackson `TreeNode`.
105+
*
106+
* These are parse trees of user-supplied JSON, which may lead to arbitrary code execution
107+
* if passed to an unsafely-configured `ObjectMapper`'s `treeToValue` method.
108+
*/
109+
predicate createJacksonTreeNodeStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
110+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
111+
m.getDeclaringType() instanceof ObjectMapper and
112+
m.hasName("readTree") and
113+
ma.getArgument(0) = fromNode.asExpr() and
114+
ma = toNode.asExpr()
115+
)
116+
or
117+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
118+
m.getDeclaringType() instanceof JsonParser and
119+
m.hasName("readValueAsTree") and
120+
ma.getQualifier() = fromNode.asExpr() and
121+
ma = toNode.asExpr()
122+
)
123+
}
124+
125+
/**
126+
* Holds if `type` or one of its supertypes has a field with `JsonTypeInfo` annotation
127+
* that enables polymorphic type handling.
128+
*/
129+
private predicate hasJsonTypeInfoAnnotation(RefType type) {
130+
hasFieldWithJsonTypeAnnotation(type.getASupertype*()) or
131+
hasJsonTypeInfoAnnotation(type.getAField().getType())
132+
}
133+
134+
/**
135+
* Holds if `type` has a field with `JsonTypeInfo` annotation
136+
* that enables polymorphic type handling.
137+
*/
138+
private predicate hasFieldWithJsonTypeAnnotation(RefType type) {
139+
exists(Annotation a |
140+
type.getAField().getAnAnnotation() = a and
141+
a.getType().hasQualifiedName("com.fasterxml.jackson.annotation", "JsonTypeInfo") and
142+
a.getValue("use").(VarAccess).getVariable().hasName(["CLASS", "MINIMAL_CLASS"])
143+
)
144+
}
145+
146+
/**
147+
* Holds if `call` is a method call to a Jackson deserialization method such as `ObjectMapper.readValue(String, Class)`,
148+
* and the target deserialized class has a field with a `JsonTypeInfo` annotation that enables polymorphic typing.
149+
*/
150+
predicate hasArgumentWithUnsafeJacksonAnnotation(MethodAccess call) {
151+
call.getMethod() instanceof ObjectMapperReadMethod and
152+
exists(RefType argType, int i | i > 0 and argType = call.getArgument(i).getType() |
153+
hasJsonTypeInfoAnnotation(argType.(ParameterizedType).getATypeArgument())
154+
)
155+
}
156+
157+
/**
158+
* Holds if `fromNode` to `toNode` is a dataflow step that looks like resolving a class.
159+
* A method probably resolves a class if it takes a string, returns a type descriptor,
160+
* and its name contains "resolve", "load", etc.
161+
*
162+
* Any method call that satisfies the rule above is assumed to propagate taint from its string arguments,
163+
* so methods that accept user-controlled data but sanitize it or use it for some
164+
* completely different purpose before returning a type descriptor could result in false positives.
165+
*/
166+
predicate looksLikeResolveClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
167+
exists(MethodAccess ma, Method m, Expr arg | m = ma.getMethod() and arg = ma.getAnArgument() |
168+
m.getReturnType() instanceof JacksonTypeDescriptorType and
169+
m.getName().toLowerCase().regexpMatch("(.*)(resolve|load|class|type)(.*)") and
170+
arg.getType() instanceof TypeString and
171+
arg = fromNode.asExpr() and
172+
ma = toNode.asExpr()
173+
)
174+
}

0 commit comments

Comments
 (0)