Skip to content

Commit 3eb2af1

Browse files
First draft of sinks for unsafe deserialization with Jackson
1 parent 1d56748 commit 3eb2af1

2 files changed

Lines changed: 148 additions & 0 deletions

File tree

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ class UnsafeDeserializationConfig extends TaintTracking::Configuration {
4040
ma.getArgument(0) = pred.asExpr() and
4141
ma.getQualifier() = succ.asExpr()
4242
)
43+
or
44+
createJacksonJsonParserStep(pred, succ)
45+
or
46+
createJacksonTreeNodeStep(pred, succ)
4347
}
4448

4549
override predicate isSanitizer(DataFlow::Node node) {

java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,61 @@ class XMLDecoderReadObjectMethod extends Method {
2323
}
2424
}
2525

26+
class ObjectMapperReadMethod extends Method {
27+
ObjectMapperReadMethod() {
28+
this.getDeclaringType() instanceof ObjectMapper and
29+
this.hasName(["readValue", "readValues", "treeToValue"])
30+
}
31+
}
32+
33+
class ObjectMapper extends RefType {
34+
ObjectMapper() { hasQualifiedName("com.fasterxml.jackson.databind", "ObjectMapper") }
35+
}
36+
37+
class MapperBuilder extends RefType {
38+
MapperBuilder() {
39+
hasQualifiedName("com.fasterxml.jackson.databind.cfg", "MapperBuilder<JsonMapper,Builder>")
40+
}
41+
}
42+
43+
class JsonFactory extends RefType {
44+
JsonFactory() { hasQualifiedName("com.fasterxml.jackson.core", "JsonFactory") }
45+
}
46+
47+
class JsonParser extends RefType {
48+
JsonParser() { hasQualifiedName("com.fasterxml.jackson.core", "JsonParser") }
49+
}
50+
51+
class EnableJacksonDefaultTyping extends MethodAccess {
52+
EnableJacksonDefaultTyping() {
53+
this.getMethod().getDeclaringType() instanceof ObjectMapper and
54+
this.getMethod().hasName("enableDefaultTyping")
55+
}
56+
}
57+
58+
class ObjectMapperReadSink extends DataFlow::ExprNode {
59+
ObjectMapperReadSink() {
60+
exists(MethodAccess ma | ma.getQualifier() = this.asExpr() |
61+
ma.getMethod() instanceof ObjectMapperReadMethod
62+
)
63+
}
64+
}
65+
66+
class SetPolymorphicTypeValidatorSource extends DataFlow::ExprNode {
67+
SetPolymorphicTypeValidatorSource() {
68+
exists(MethodAccess ma, Method m, Expr q | m = ma.getMethod() and q = ma.getQualifier() |
69+
(
70+
m.getDeclaringType() instanceof ObjectMapper and
71+
m.hasName("setPolymorphicTypeValidator")
72+
or
73+
m.getDeclaringType() instanceof MapperBuilder and
74+
m.hasName("polymorphicTypeValidator")
75+
) and
76+
this.asExpr() = [q, q.(VarAccess).getVariable().getAnAccess()]
77+
)
78+
}
79+
}
80+
2681
class SafeXStream extends DataFlow2::Configuration {
2782
SafeXStream() { this = "UnsafeDeserialization::SafeXStream" }
2883

@@ -114,6 +169,87 @@ class SafeKryo extends DataFlow2::Configuration {
114169
}
115170
}
116171

172+
class EnabledJacksonDefaultTyping extends DataFlow2::Configuration {
173+
EnabledJacksonDefaultTyping() { this = "EnabledJacksonDefaultTyping" }
174+
175+
override predicate isSource(DataFlow::Node src) {
176+
any(EnableJacksonDefaultTyping ma).getQualifier().(VarAccess).getVariable().getAnAccess() =
177+
src.asExpr()
178+
}
179+
180+
override predicate isSink(DataFlow::Node sink) { sink instanceof ObjectMapperReadSink }
181+
}
182+
183+
class SafeObjectMapper extends DataFlow2::Configuration {
184+
SafeObjectMapper() { this = "SafeObjectMapper" }
185+
186+
override predicate isSource(DataFlow::Node src) {
187+
src instanceof SetPolymorphicTypeValidatorSource
188+
}
189+
190+
override predicate isSink(DataFlow::Node sink) { sink instanceof ObjectMapperReadSink }
191+
192+
/**
193+
* Holds if `fromNode` to `toNode` is a dataflow step
194+
* that configures or creates an `ObjectMapper` via a builder.
195+
*/
196+
override predicate isAdditionalFlowStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
197+
exists(MethodAccess ma, Method m, Expr q | m = ma.getMethod() and q = ma.getQualifier() |
198+
m.getDeclaringType() instanceof MapperBuilder and
199+
m.getReturnType()
200+
.(RefType)
201+
.hasQualifiedName("com.fasterxml.jackson.databind.json",
202+
["JsonMapper$Builder", "JsonMapper"]) and
203+
fromNode.asExpr() = [q, q.(VarAccess).getVariable().getAnAccess()] and
204+
ma = toNode.asExpr()
205+
)
206+
}
207+
}
208+
209+
/**
210+
* Holds if `fromNode` to `toNode` is a dataflow step that creates a Jackson parser.
211+
*/
212+
predicate createJacksonJsonParserStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
213+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
214+
(m.getDeclaringType() instanceof ObjectMapper or m.getDeclaringType() instanceof JsonFactory) and
215+
m.hasName("createParser") and
216+
ma.getArgument(0) = fromNode.asExpr() and
217+
ma = toNode.asExpr()
218+
)
219+
}
220+
221+
/**
222+
* Holds if `fromNode` to `toNode` is a dataflow step that creates a Jackson `TreeNode`.
223+
*/
224+
predicate createJacksonTreeNodeStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
225+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
226+
m.getDeclaringType() instanceof ObjectMapper and
227+
m.hasName("readTree") and
228+
ma.getArgument(0) = fromNode.asExpr() and
229+
ma = toNode.asExpr()
230+
)
231+
or
232+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
233+
m.getDeclaringType() instanceof JsonParser and
234+
m.hasName("readValueAsTree") and
235+
ma.getQualifier() = fromNode.asExpr() and
236+
ma = toNode.asExpr()
237+
)
238+
}
239+
240+
predicate hasJsonTypeInfoAnnotation(RefType type) {
241+
hasFieldWithJsonTypeAnnotation(type.getASupertype*()) or
242+
hasFieldWithJsonTypeAnnotation(type.getAField().getType())
243+
}
244+
245+
predicate hasFieldWithJsonTypeAnnotation(RefType type) {
246+
exists(Annotation a |
247+
type.getAField().getAnAnnotation() = a and
248+
a.getType().hasQualifiedName("com.fasterxml.jackson.annotation", "JsonTypeInfo") and
249+
a.getValue("use").(VarAccess).getVariable().hasName("CLASS")
250+
)
251+
}
252+
117253
predicate unsafeDeserialization(MethodAccess ma, Expr sink) {
118254
exists(Method m | m = ma.getMethod() |
119255
m instanceof ObjectInputStreamReadObjectMethod and
@@ -162,6 +298,14 @@ predicate unsafeDeserialization(MethodAccess ma, Expr sink) {
162298
ma.getMethod() instanceof CastorUnmarshalMethod and sink = ma.getAnArgument()
163299
or
164300
ma.getMethod() instanceof BurlapInputReadObjectMethod and sink = ma.getQualifier()
301+
or
302+
ma.getMethod() instanceof ObjectMapperReadMethod and
303+
sink = ma.getArgument(0) and
304+
(
305+
exists(EnabledJacksonDefaultTyping config | config.hasFlowToExpr(ma.getQualifier())) or
306+
hasJsonTypeInfoAnnotation(ma.getArgument(1).getType().(ParameterizedType).getATypeArgument())
307+
) and
308+
not exists(SafeObjectMapper config | config.hasFlowToExpr(ma.getQualifier()))
165309
)
166310
}
167311

0 commit comments

Comments
 (0)