Skip to content

Commit 476843a

Browse files
Added comments for Jackson in UnsafeDeserialization.qll
1 parent e9731cd commit 476843a

File tree

1 file changed

+37
-3
lines changed

1 file changed

+37
-3
lines changed

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ class SafeKryo extends DataFlow2::Configuration {
181181
}
182182
}
183183

184+
/**
185+
* Tracks flow from `enableDefaultTyping` calls to a subsequent Jackson deserialization method call.
186+
*/
184187
private class EnableJacksonDefaultTypingConfig extends DataFlow2::Configuration {
185188
EnableJacksonDefaultTypingConfig() { this = "EnableJacksonDefaultTypingConfig" }
186189

@@ -191,6 +194,12 @@ private class EnableJacksonDefaultTypingConfig extends DataFlow2::Configuration
191194
override predicate isSink(DataFlow::Node sink) { sink instanceof ObjectMapperReadSink }
192195
}
193196

197+
/**
198+
* Tracks flow from calls, which set a type validator, to a subsequent Jackson deserialization method call,
199+
* including across builder method calls.
200+
*
201+
* Such a Jackson deserialization method call is safe because validation will likely prevent instantiating unexpected types.
202+
*/
194203
private class SafeObjectMapperConfig extends DataFlow2::Configuration {
195204
SafeObjectMapperConfig() { this = "SafeObjectMapperConfig" }
196205

@@ -217,6 +226,12 @@ private class SafeObjectMapperConfig extends DataFlow2::Configuration {
217226
}
218227
}
219228

229+
/**
230+
* Tracks flow from a remote source to a type descriptor (e.g. a `java.lang.Class` instance)
231+
* passed to a Jackson deserialization method.
232+
*
233+
* If this is user-controlled, arbitrary code could be executed while instantiating the user-specified type.
234+
*/
220235
private class UnsafeTypeConfig extends TaintTracking2::Configuration {
221236
UnsafeTypeConfig() { this = "UnsafeTypeConfig" }
222237

@@ -232,6 +247,10 @@ private class UnsafeTypeConfig extends TaintTracking2::Configuration {
232247

233248
/**
234249
* Holds if `fromNode` to `toNode` is a dataflow step that looks like resolving a class.
250+
*
251+
* Note any method that returns a `Class` or similar is assumed to propagate taint from all
252+
* of its arguments, so methods that accept user-controlled data but sanitize it or use it for some
253+
* completely different purpose before returning a `Class` could result in false positives.
235254
*/
236255
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
237256
exists(MethodAccess ma, RefType returnType | returnType = ma.getMethod().getReturnType() |
@@ -244,6 +263,9 @@ private class UnsafeTypeConfig extends TaintTracking2::Configuration {
244263

245264
/**
246265
* Holds if `fromNode` to `toNode` is a dataflow step that creates a Jackson parser.
266+
*
267+
* For example, a `createParser(userString)` call yields a `JsonParser` which becomes dangerous
268+
* if passed to an unsafely-configured `ObjectMapper`'s `readValue` method.
247269
*/
248270
predicate createJacksonJsonParserStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
249271
exists(MethodAccess ma, Method m | m = ma.getMethod() |
@@ -256,6 +278,9 @@ predicate createJacksonJsonParserStep(DataFlow::Node fromNode, DataFlow::Node to
256278

257279
/**
258280
* Holds if `fromNode` to `toNode` is a dataflow step that creates a Jackson `TreeNode`.
281+
*
282+
* These are parse trees of user-supplied JSON, which may lead to arbitrary code execution
283+
* if passed to an unsafely-configured `ObjectMapper`'s `treeToValue` method.
259284
*/
260285
predicate createJacksonTreeNodeStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
261286
exists(MethodAccess ma, Method m | m = ma.getMethod() |
@@ -294,6 +319,17 @@ private predicate hasFieldWithJsonTypeAnnotation(RefType type) {
294319
)
295320
}
296321

322+
/**
323+
* Holds if `call` is a method call to a Jackson deserialization method such as `ObjectMapper.readValue(String, Class)`,
324+
* and the target deserialized class has a field with a `JsonTypeInfo` annotation that enables polymorphic typing.
325+
*/
326+
private predicate hasArgumentWithUnsafeJacksonAnnotation(MethodAccess call) {
327+
call.getMethod() instanceof ObjectMapperReadMethod and
328+
exists(RefType argType, int i | i > 0 and argType = call.getArgument(i).getType() |
329+
hasJsonTypeInfoAnnotation(argType.(ParameterizedType).getATypeArgument())
330+
)
331+
}
332+
297333
predicate unsafeDeserialization(MethodAccess ma, Expr sink) {
298334
exists(Method m | m = ma.getMethod() |
299335
m instanceof ObjectInputStreamReadObjectMethod and
@@ -350,9 +386,7 @@ predicate unsafeDeserialization(MethodAccess ma, Expr sink) {
350386
or
351387
exists(EnableJacksonDefaultTypingConfig config | config.hasFlowToExpr(ma.getQualifier()))
352388
or
353-
exists(RefType argType, int i | i > 0 and argType = ma.getArgument(i).getType() |
354-
hasJsonTypeInfoAnnotation(argType.(ParameterizedType).getATypeArgument())
355-
)
389+
hasArgumentWithUnsafeJacksonAnnotation(ma)
356390
) and
357391
not exists(SafeObjectMapperConfig config | config.hasFlowToExpr(ma.getQualifier()))
358392
)

0 commit comments

Comments
 (0)