Unsafe deserialization: add support for Jodd JSON library

This commit is contained in:
Chris Smowton
2021-08-05 16:01:14 +01:00
parent 6471092139
commit 0b6c991ac4
12 changed files with 1294 additions and 2 deletions

View File

@@ -85,6 +85,7 @@ private module Frameworks {
private import semmle.code.java.frameworks.jackson.JacksonSerializability
private import semmle.code.java.frameworks.JavaxJson
private import semmle.code.java.frameworks.JaxWS
private import semmle.code.java.frameworks.JoddJson
private import semmle.code.java.frameworks.JsonJava
private import semmle.code.java.frameworks.Optional
private import semmle.code.java.frameworks.spring.SpringCache

View File

@@ -0,0 +1,68 @@
/**
* Provides classes and predicates for working with the Jodd JSON framework.
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
/** The class `jodd.json.Parser`. */
class JoddJsonParser extends RefType {
JoddJsonParser() { this.hasQualifiedName("jodd.json", "JsonParser") }
}
/** A `JsonParser.parse*` deserialization method. */
class JoddJsonParseMethod extends Method {
JoddJsonParseMethod() {
this.getDeclaringType() instanceof JoddJsonParser and
this.getName().matches("parse%")
}
}
/** The `JsonParser.setClassMetadataName` method. */
class SetClassMetadataNameMethod extends Method {
SetClassMetadataNameMethod() {
this.getDeclaringType() instanceof JoddJsonParser and
this.hasName("setClassMetadataName")
}
}
/** The `JsonParser.withClassMetadata` method. */
class WithClassMetadataMethod extends Method {
WithClassMetadataMethod() {
this.getDeclaringType() instanceof JoddJsonParser and
this.hasName("withClassMetadata")
}
}
/** The `JsonParser.allowClass` method. */
class AllowClassMethod extends Method {
AllowClassMethod() {
this.getDeclaringType() instanceof JoddJsonParser and
this.hasName("allowClass")
}
}
/**
* A partial model of jodd.json.JsonParser noting fluent methods.
*
* This means that DataFlow::localFlow and similar methods are aware
* that the result of (e.g.) JsonParser.allowClass is an alias of the
* qualifier.
*/
private class JsonParserFluentMethods extends SummaryModelCsv {
override predicate row(string s) {
s =
[
"jodd.json;JsonParser;false;allowAllClasses;;;Argument[-1];ReturnValue;value",
"jodd.json;JsonParser;false;allowClass;;;Argument[-1];ReturnValue;value",
"jodd.json;JsonParser;false;lazy;;;Argument[-1];ReturnValue;value",
"jodd.json;JsonParser;false;looseMode;;;Argument[-1];ReturnValue;value",
"jodd.json;JsonParser;false;map;;;Argument[-1];ReturnValue;value",
"jodd.json;JsonParser;false;setClassMetadataName;;;Argument[-1];ReturnValue;value",
"jodd.json;JsonParser;false;strictTypes;;;Argument[-1];ReturnValue;value",
"jodd.json;JsonParser;false;useAltPaths;;;Argument[-1];ReturnValue;value",
"jodd.json;JsonParser;false;withClassMetadata;;;Argument[-1];ReturnValue;value",
"jodd.json;JsonParser;false;withValueConverter;;;Argument[-1];ReturnValue;value"
]
}
}

View File

@@ -15,6 +15,7 @@ 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.Jabsorb
private import semmle.code.java.frameworks.JoddJson
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.Reflection
@@ -192,6 +193,16 @@ predicate unsafeDeserialization(MethodAccess ma, Expr sink) {
or
m instanceof JabsorbFromJsonMethod and
sink = ma.getArgument(0)
or
m instanceof JoddJsonParseMethod and
sink = ma.getArgument(0) and
(
// User controls the target type for deserialization
any(UnsafeTypeConfig c).hasFlowToExpr(ma.getArgument(1))
or
// jodd.json.JsonParser may be configured for unrestricted deserialization to user-specified types
joddJsonParserConfiguredUnsafely(ma.getQualifier())
)
)
}
@@ -248,6 +259,17 @@ class UnsafeDeserializationConfig extends TaintTracking::Configuration {
ma.getArgument(0) = node.asExpr() and
exists(SafeJsonIoConfig sji | sji.hasFlowToExpr(ma.getArgument(1)))
)
or
exists(MethodAccess ma |
// Sanitize the input to jodd.json.JsonParser.parse et al whenever it appears
// to be called with an explicit class argument limiting those types that can
// be instantiated during deserialization.
ma.getMethod() instanceof JoddJsonParseMethod and
ma.getArgument(1).getType() instanceof TypeClass and
not ma.getArgument(1) instanceof NullLiteral and
not ma.getArgument(1).getType().getName() = ["Class<Object>", "Class<?>"] and
node.asExpr() = ma.getAnArgument()
)
}
}
@@ -295,6 +317,8 @@ class UnsafeTypeConfig extends TaintTracking2::Configuration {
ma.getMethod() instanceof ObjectMapperReadMethod
or
ma.getMethod() instanceof JabsorbUnmarshallMethod
or
ma.getMethod() instanceof JoddJsonParseMethod
) and
// Note `JacksonTypeDescriptorType` includes plain old `java.lang.Class`
arg.getType() instanceof JacksonTypeDescriptorType and
@@ -356,3 +380,85 @@ class SafeObjectMapperConfig extends DataFlow2::Configuration {
)
}
}
/**
* A method that configures Jodd's JsonParser, either enabling dangerous deserialization to
* arbitrary Java types or restricting the types that can be instantiated.
*/
private class JoddJsonParserConfigurationMethodQualifier extends DataFlow::ExprNode {
JoddJsonParserConfigurationMethodQualifier() {
exists(MethodAccess ma, Method m | ma.getQualifier() = this.asExpr() and m = ma.getMethod() |
m instanceof WithClassMetadataMethod
or
m instanceof SetClassMetadataNameMethod
or
m instanceof AllowClassMethod
)
}
}
/**
* Configuration tracking flow from methods that configure `jodd.json.JsonParser`'s class
* instantiation feature to a `.parse` call on the same parser.
*/
private class JoddJsonParserConfigurationMethodConfig extends DataFlow2::Configuration {
JoddJsonParserConfigurationMethodConfig() {
this = "UnsafeDeserialization::JoddJsonParserConfigurationMethodConfig"
}
override predicate isSource(DataFlow::Node src) {
src instanceof JoddJsonParserConfigurationMethodQualifier
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
ma.getMethod() instanceof JoddJsonParseMethod and
sink.asExpr() = ma.getQualifier() // The class type argument
)
}
}
/**
* Gets the qualifier to a method call that configures a `jodd.json.JsonParser` instance unsafely.
*
* Such a parser may instantiate an arbtirary type when deserializing untrusted data.
*/
private DataFlow::Node getAnUnsafelyConfiguredParser() {
exists(MethodAccess ma | result.asExpr() = ma.getQualifier() |
ma.getMethod() instanceof WithClassMetadataMethod and
ma.getArgument(0).(CompileTimeConstantExpr).getBooleanValue() = true
or
ma.getMethod() instanceof SetClassMetadataNameMethod and
not ma.getArgument(0) instanceof NullLiteral
)
}
/**
* Gets the qualifier to a method call that configures a `jodd.json.JsonParser` instance safely.
*
* Such a parser will not instantiate an arbtirary type when deserializing untrusted data.
*/
private DataFlow::Node getASafelyConfiguredParser() {
exists(MethodAccess ma | result.asExpr() = ma.getQualifier() |
ma.getMethod() instanceof WithClassMetadataMethod and
ma.getArgument(0).(CompileTimeConstantExpr).getBooleanValue() = false
or
ma.getMethod() instanceof SetClassMetadataNameMethod and
ma.getArgument(0) instanceof NullLiteral
or
ma.getMethod() instanceof AllowClassMethod
)
}
/**
* Holds if `parseMethodQualifierExpr` is a `jodd.json.JsonParser` instance that is configured unsafely
* and which never appears to be configured safely.
*/
private predicate joddJsonParserConfiguredUnsafely(Expr parserExpr) {
exists(DataFlow::Node parser, JoddJsonParserConfigurationMethodConfig config |
parser.asExpr() = parserExpr
|
config.hasFlow(getAnUnsafelyConfiguredParser(), parser) and
not config.hasFlow(getASafelyConfiguredParser(), parser)
)
}