Merge branch 'main' into atorralba/promote-groovy-injection

This commit is contained in:
Tony Torralba
2021-08-03 09:53:46 +02:00
committed by GitHub
211 changed files with 5560 additions and 2147 deletions

View File

@@ -35,6 +35,7 @@ predicate instantiates(RefType t, GenericType g, int i, RefType arg) {
* - a class `MyIntMap<V> extends HashMap<Integer, V>` instantiates `Map` (among others)
* with the `0`-th type parameter being `Integer` and the `1`-th type parameter being `V`.
*/
pragma[nomagic]
predicate indirectlyInstantiates(RefType t, GenericType g, int i, RefType arg) {
instantiates(t, g, i, arg)
or

View File

@@ -62,7 +62,7 @@ predicate depends(RefType t, RefType dep) {
or
// the type of a type literal accessed in `t`,
exists(TypeLiteral l | l.getEnclosingCallable().getDeclaringType() = t |
usesType(l.getTypeName().getType(), dep)
usesType(l.getReferencedType(), dep)
)
or
// the type of an annotation (or one of its element values) that annotates `t` or one of its members,
@@ -76,7 +76,7 @@ predicate depends(RefType t, RefType dep) {
or
// the type accessed in an `instanceof` expression in `t`.
exists(InstanceOfExpr ioe | t = ioe.getEnclosingCallable().getDeclaringType() |
usesType(ioe.getTypeName().getType(), dep)
usesType(ioe.getCheckedType(), dep)
)
)
}

View File

@@ -80,7 +80,7 @@ predicate numDepends(RefType t, RefType dep, int value) {
elem = l and
l.getEnclosingCallable().getDeclaringType() = t
|
usesType(l.getTypeName().getType(), dep)
usesType(l.getReferencedType(), dep)
)
or
// the type of an annotation (or one of its element values) that annotates `t` or one of its members,
@@ -100,7 +100,7 @@ predicate numDepends(RefType t, RefType dep, int value) {
elem = ioe and
t = ioe.getEnclosingCallable().getDeclaringType()
|
usesType(ioe.getTypeName().getType(), dep)
usesType(ioe.getCheckedType(), dep)
)
)
}

View File

@@ -46,17 +46,6 @@ class Expr extends ExprParent, @expr {
*/
int getKind() { exprs(this, result, _, _, _) }
/**
* DEPRECATED: This is no longer necessary. See `Expr.isParenthesized()`.
*
* Gets this expression with any surrounding parentheses removed.
*/
deprecated Expr getProperExpr() {
result = this.(ParExpr).getExpr().getProperExpr()
or
result = this and not this instanceof ParExpr
}
/** Gets the statement containing this expression, if any. */
Stmt getEnclosingStmt() { statementEnclosingExpr(this, result) }
@@ -1318,19 +1307,6 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
override string getAPrimaryQlClass() { result = "SwitchExpr" }
}
/**
* DEPRECATED: Use `Expr.isParenthesized()` instead.
*
* A parenthesised expression.
*/
deprecated class ParExpr extends Expr, @parexpr {
/** Gets the expression inside the parentheses. */
deprecated Expr getExpr() { result.getParent() = this }
/** Gets a printable representation of this expression. */
override string toString() { result = "(...)" }
}
/** An `instanceof` expression. */
class InstanceOfExpr extends Expr, @instanceofexpr {
/** Gets the expression on the left-hand side of the `instanceof` operator. */
@@ -1357,6 +1333,9 @@ class InstanceOfExpr extends Expr, @instanceofexpr {
/** Gets the access to the type on the right-hand side of the `instanceof` operator. */
Expr getTypeName() { result.isNthChildOf(this, 1) }
/** Gets the type this `instanceof` expression checks for. */
RefType getCheckedType() { result = getTypeName().getType() }
/** Gets a printable representation of this expression. */
override string toString() { result = "...instanceof..." }
@@ -1449,6 +1428,12 @@ class TypeLiteral extends Expr, @typeliteral {
/** Gets the access to the type whose class is accessed. */
Expr getTypeName() { result.getParent() = this }
/**
* Gets the type this type literal refers to. For example for `String.class` the
* result is the type representing `String`.
*/
Type getReferencedType() { result = getTypeName().getType() }
/** Gets a printable representation of this expression. */
override string toString() { result = this.getTypeName().toString() + ".class" }

View File

@@ -47,19 +47,22 @@ private XMLElement elementReferencingType(RefType rt) {
}
abstract private class ReflectiveClassIdentifier extends Expr {
/**
* Gets the type of a class identified by this expression.
*/
abstract RefType getReflectivelyIdentifiedClass();
}
private class ReflectiveClassIdentifierLiteral extends ReflectiveClassIdentifier, TypeLiteral {
override RefType getReflectivelyIdentifiedClass() {
result = getTypeName().getType().(RefType).getSourceDeclaration()
result = getReferencedType().(RefType).getSourceDeclaration()
}
}
/**
* A call to a Java standard library method which constructs or returns a `Class<T>` from a `String`.
*/
library class ReflectiveClassIdentifierMethodAccess extends ReflectiveClassIdentifier, MethodAccess {
class ReflectiveClassIdentifierMethodAccess extends ReflectiveClassIdentifier, MethodAccess {
ReflectiveClassIdentifierMethodAccess() {
// A call to `Class.forName(...)`, from which we can infer `T` in the returned type `Class<T>`.
getCallee().getDeclaringType() instanceof TypeClass and getCallee().hasName("forName")
@@ -314,6 +317,26 @@ class ClassMethodAccess extends MethodAccess {
}
}
/**
* A call to `Class.getConstructors(..)` or `Class.getDeclaredConstructors(..)`.
*/
class ReflectiveConstructorsAccess extends ClassMethodAccess {
ReflectiveConstructorsAccess() {
this.getCallee().hasName("getConstructors") or
this.getCallee().hasName("getDeclaredConstructors")
}
}
/**
* A call to `Class.getMethods(..)` or `Class.getDeclaredMethods(..)`.
*/
class ReflectiveMethodsAccess extends ClassMethodAccess {
ReflectiveMethodsAccess() {
this.getCallee().hasName("getMethods") or
this.getCallee().hasName("getDeclaredMethods")
}
}
/**
* A call to `Class.getMethod(..)` or `Class.getDeclaredMethod(..)`.
*/

View File

@@ -168,7 +168,7 @@ class TestNGTestMethod extends Method {
or
// Or the data provider class should be declared
result.getDeclaringType() =
testAnnotation.getValue("dataProviderClass").(TypeLiteral).getTypeName().getType()
testAnnotation.getValue("dataProviderClass").(TypeLiteral).getReferencedType()
)
}
}
@@ -227,7 +227,7 @@ class TestNGListenersAnnotation extends TestNGAnnotation {
* Gets a listener defined in this annotation.
*/
TestNGListenerImpl getAListener() {
result = getAValue("value").(TypeLiteral).getTypeName().getType()
result = getAValue("value").(TypeLiteral).getReferencedType()
}
}
@@ -303,7 +303,7 @@ class JUnitCategoryAnnotation extends Annotation {
literal = value.(ArrayCreationExpr).getInit().getAnInit()
)
|
result = literal.getTypeName().getType()
result = literal.getReferencedType()
)
}
}

View File

@@ -99,6 +99,7 @@ private module Frameworks {
private import semmle.code.java.security.GroovyInjection
private import semmle.code.java.security.JexlInjectionSinkModels
private import semmle.code.java.security.LdapInjection
private import semmle.code.java.security.MvelInjection
private import semmle.code.java.security.XPath
private import semmle.code.java.frameworks.android.SQLite
private import semmle.code.java.frameworks.Jdbc

View File

@@ -25,8 +25,8 @@ Expr enumConstEquality(Expr e, boolean polarity, EnumConstant c) {
}
/** Gets an instanceof expression of `v` with type `type` */
InstanceOfExpr instanceofExpr(SsaVariable v, Type type) {
result.getTypeName().getType() = type and
InstanceOfExpr instanceofExpr(SsaVariable v, RefType type) {
result.getCheckedType() = type and
result.getExpr() = v.getAUse()
}

View File

@@ -504,7 +504,7 @@ private predicate correlatedConditions(
inverted = pol1.booleanXor(pol2)
)
or
exists(SsaVariable v, Type type |
exists(SsaVariable v, RefType type |
cond1.getCondition() = instanceofExpr(v, type) and
cond2.getCondition() = instanceofExpr(v, type) and
inverted = false

View File

@@ -299,7 +299,7 @@ private predicate downcastSuccessor(VarAccess va, RefType t) {
private predicate instanceOfGuarded(VarAccess va, RefType t) {
exists(InstanceOfExpr ioe, BaseSsaVariable v |
ioe.getExpr() = v.getAUse() and
t = ioe.getTypeName().getType() and
t = ioe.getCheckedType() and
va = v.getAUse() and
guardControls_v1(ioe, va.getBasicBlock(), true)
)
@@ -311,7 +311,7 @@ private predicate instanceOfGuarded(VarAccess va, RefType t) {
predicate arrayInstanceOfGuarded(ArrayAccess aa, RefType t) {
exists(InstanceOfExpr ioe, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 |
ioe.getExpr() = aa1 and
t = ioe.getTypeName().getType() and
t = ioe.getCheckedType() and
aa1.getArray() = v1.getAUse() and
aa1.getIndexExpr() = v2.getAUse() and
aa.getArray() = v1.getAUse() and

View File

@@ -368,7 +368,44 @@ private class ContainerFlowSummaries extends SummaryModelCsv {
"java.util;Collections;false;copy;(List,List);;Element of Argument[1];Element of Argument[0];value",
"java.util;Collections;false;fill;(List,Object);;Argument[1];Element of Argument[0];value",
"java.util;Arrays;false;asList;;;ArrayElement of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;addAll;(Collection,Object[]);;ArrayElement of Argument[1];Element of Argument[0];value"
"java.util;Collections;false;addAll;(Collection,Object[]);;ArrayElement of Argument[1];Element of Argument[0];value",
"java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Object,Object);;Argument[0];MapKey of Argument[-1];value",
"java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Object,Object);;Argument[1];MapValue of Argument[-1];value",
"java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Entry);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Entry);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Object,Object);;Argument[0];MapKey of Argument[-1];value",
"java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Object,Object);;Argument[1];MapValue of Argument[-1];value",
"java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Entry);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Entry);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;ArrayDeque;false;ArrayDeque;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;ArrayList;false;ArrayList;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;EnumMap;false;EnumMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;EnumMap;false;EnumMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;EnumMap;false;EnumMap;(EnumMap);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;EnumMap;false;EnumMap;(EnumMap);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;HashMap;false;HashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;HashMap;false;HashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;HashSet;false;HashSet;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;Hashtable;false;Hashtable;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;Hashtable;false;Hashtable;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;IdentityHashMap;false;IdentityHashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;IdentityHashMap;false;IdentityHashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;LinkedHashMap;false;LinkedHashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;LinkedHashMap;false;LinkedHashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;LinkedHashSet;false;LinkedHashSet;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;LinkedList;false;LinkedList;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;PriorityQueue;false;PriorityQueue;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;PriorityQueue;false;PriorityQueue;(PriorityQueue);;Element of Argument[0];Element of Argument[-1];value",
"java.util;PriorityQueue;false;PriorityQueue;(SortedSet);;Element of Argument[0];Element of Argument[-1];value",
"java.util;TreeMap;false;TreeMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;TreeMap;false;TreeMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;TreeMap;false;TreeMap;(SortedMap);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;TreeMap;false;TreeMap;(SortedMap);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;TreeSet;false;TreeSet;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;TreeSet;false;TreeSet;(SortedSet);;Element of Argument[0];Element of Argument[-1];value",
"java.util;Vector;false;Vector;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;WeakHashMap;false;WeakHashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;WeakHashMap;false;WeakHashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value"
]
}
}

View File

@@ -101,7 +101,7 @@ private module DispatchImpl {
* restricted to those `ma`s for which a context might make a difference.
*/
Method viableImplInCallContext(MethodAccess ma, Call ctx) {
result = VirtualDispatch::viableImpl(ma) and
result = viableCallable(ma) and
exists(int i, Callable c, Method def, RefType t, boolean exact |
mayBenefitFromCallContext(ma, c, i) and
c = viableCallable(ctx) and
@@ -115,6 +115,8 @@ private module DispatchImpl {
result = VirtualDispatch::viableMethodImpl(def, t.getSourceDeclaration(), t2) and
not failsUnification(t, t2)
)
or
result = def and def instanceof SummarizedCallable
)
}

View File

@@ -176,7 +176,7 @@ private module Dispatch {
v.getAUse() = q and
guardControls_v1(ioe, q.getBasicBlock(), false) and
ioe.getExpr() = v.getAUse() and
ioe.getTypeName().getType().getErasure() = t and
ioe.getCheckedType().getErasure() = t and
tgt.getDeclaringType().getSourceDeclaration().getASourceSupertype*() = t
)
}

View File

@@ -64,5 +64,5 @@ class RunWithAnnotation extends Annotation {
/**
* Gets the runner that will be used.
*/
Type getRunner() { result = getValue("value").(TypeLiteral).getTypeName().getType() }
Type getRunner() { result = getValue("value").(TypeLiteral).getReferencedType() }
}

View File

@@ -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<JsonMapper,Builder>")
}
}
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()
)
}

View File

@@ -33,7 +33,7 @@ class HttpResponseParseAsDeserializableField extends DeserializableField {
HttpResponseParseAsDeserializableField() {
exists(RefType decltype, TypeLiteralToParseAsFlowConfiguration conf |
decltype = getDeclaringType() and
conf.getSourceWithFlowToParseAs().getTypeName().getType() = decltype and
conf.getSourceWithFlowToParseAs().getReferencedType() = decltype and
decltype.fromSource()
)
}

View File

@@ -112,7 +112,7 @@ private class TypeLiteralToJacksonDatabindFlowConfiguration extends DataFlow5::C
private class ExplicitlyReadJacksonDeserializableType extends JacksonDeserializableType {
ExplicitlyReadJacksonDeserializableType() {
exists(TypeLiteralToJacksonDatabindFlowConfiguration conf |
usesType(conf.getSourceWithFlowToJacksonDatabind().getTypeName().getType(), this)
usesType(conf.getSourceWithFlowToJacksonDatabind().getReferencedType(), this)
)
or
exists(MethodAccess ma |

View File

@@ -199,7 +199,7 @@ abstract class EjbInterfaceAnnotation extends Annotation {
// within the "value" element of this annotation.
// Uses `getAChildExpr*()` since the "value" element can have type `Class` or `Class[]`.
exists(TypeLiteral tl | tl = getValue("value").getAChildExpr*() |
exists(TypeAccess ta | ta = tl.getTypeName() | result = ta.getType())
result = tl.getReferencedType()
)
}
}

View File

@@ -45,7 +45,7 @@ class SpringComponentScan extends Annotation {
// Base package classes are type literals whose package should be considered a base package.
typeLiteral = getAValue("basePackageClasses")
|
result = typeLiteral.getTypeName().getType().(RefType).getPackage().getName()
result = typeLiteral.getReferencedType().(RefType).getPackage().getName()
)
}
}

View File

@@ -0,0 +1,233 @@
/** Provides classes to reason about MVEL injection attacks. */
import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.ExternalFlow
/** A data flow sink for unvalidated user input that is used to construct MVEL expressions. */
abstract class MvelEvaluationSink extends DataFlow::Node { }
/** A sanitizer that prevents MVEL injection attacks. */
abstract class MvelInjectionSanitizer extends DataFlow::Node { }
/**
* A unit class for adding additional taint steps.
*
* Extend this class to add additional taint steps that should apply to the `MvelInjectionFlowConfig`.
*/
class MvelInjectionAdditionalTaintStep extends Unit {
/**
* Holds if the step from `node1` to `node2` should be considered a taint
* step for the `MvelInjectionFlowConfig` configuration.
*/
abstract predicate step(DataFlow::Node n1, DataFlow::Node n2);
}
/** Default sink for MVEL injection vulnerabilities. */
private class DefaultMvelEvaluationSink extends MvelEvaluationSink {
DefaultMvelEvaluationSink() { sinkNode(this, "mvel") }
}
private class DefaulMvelEvaluationSinkModel extends SinkModelCsv {
override predicate row(string row) {
row =
[
"javax.script;CompiledScript;false;eval;;;Argument[-1];mvel",
"org.mvel2;MVEL;false;eval;;;Argument[0];mvel",
"org.mvel2;MVEL;false;executeExpression;;;Argument[0];mvel",
"org.mvel2;MVEL;false;evalToBoolean;;;Argument[0];mvel",
"org.mvel2;MVEL;false;evalToString;;;Argument[0];mvel",
"org.mvel2;MVEL;false;executeAllExpression;;;Argument[0];mvel",
"org.mvel2;MVEL;false;executeSetExpression;;;Argument[0];mvel",
"org.mvel2;MVELRuntime;false;execute;;;Argument[1];mvel",
"org.mvel2.templates;TemplateRuntime;false;eval;;;Argument[0];mvel",
"org.mvel2.templates;TemplateRuntime;false;execute;;;Argument[0];mvel",
"org.mvel2.jsr223;MvelScriptEngine;false;eval;;;Argument[0];mvel",
"org.mvel2.jsr223;MvelScriptEngine;false;evaluate;;;Argument[0];mvel",
"org.mvel2.jsr223;MvelCompiledScript;false;eval;;;Argument[-1];mvel",
"org.mvel2.compiler;ExecutableStatement;false;getValue;;;Argument[-1];mvel",
"org.mvel2.compiler;CompiledExpression;false;getDirectValue;;;Argument[-1];mvel",
"org.mvel2.compiler;CompiledAccExpression;false;getValue;;;Argument[-1];mvel",
"org.mvel2.compiler;Accessor;false;getValue;;;Argument[-1];mvel"
]
}
}
/** A default sanitizer that considers numeric and boolean typed data safe for building MVEL expressions */
private class DefaultMvelInjectionSanitizer extends MvelInjectionSanitizer {
DefaultMvelInjectionSanitizer() {
this.getType() instanceof NumericType or this.getType() instanceof BooleanType
}
}
/** A set of additional taint steps to consider when taint tracking MVEL related data flows. */
private class DefaultMvelInjectionAdditionalTaintStep extends MvelInjectionAdditionalTaintStep {
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
expressionCompilationStep(node1, node2) or
createExpressionCompilerStep(node1, node2) or
expressionCompilerCompileStep(node1, node2) or
createCompiledAccExpressionStep(node1, node2) or
scriptCompileStep(node1, node2) or
createMvelCompiledScriptStep(node1, node2) or
templateCompileStep(node1, node2) or
createTemplateCompilerStep(node1, node2)
}
}
/**
* Holds if `node1` to `node2` is a dataflow step that compiles a MVEL expression
* by callilng `MVEL.compileExpression(tainted)`.
*/
private predicate expressionCompilationStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(StaticMethodAccess ma, Method m | ma.getMethod() = m |
m.getDeclaringType() instanceof MVEL and
m.hasName("compileExpression") and
ma.getAnArgument() = node1.asExpr() and
node2.asExpr() = ma
)
}
/**
* Holds if `node1` to `node2` is a dataflow step that creates `ExpressionCompiler`
* by calling `new ExpressionCompiler(tainted)`.
*/
private predicate createExpressionCompilerStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(ConstructorCall cc |
cc.getConstructedType() instanceof ExpressionCompiler and
cc = node2.asExpr() and
cc.getArgument(0) = node1.asExpr()
)
}
/**
* Holds if `node1` to `node2` is a dataflow step creates `CompiledAccExpression`
* by calling `new CompiledAccExpression(tainted, ...)`.
*/
private predicate createCompiledAccExpressionStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(ConstructorCall cc |
cc.getConstructedType() instanceof CompiledAccExpression and
cc = node2.asExpr() and
cc.getArgument(0) = node1.asExpr()
)
}
/**
* Holds if `node1` to `node2` is a dataflow step that compiles a MVEL expression
* by calling `ExpressionCompiler.compile()`.
*/
private predicate expressionCompilerCompileStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma, Method m | ma.getMethod() = m |
m.getDeclaringType() instanceof ExpressionCompiler and
m.hasName("compile") and
ma = node2.asExpr() and
ma.getQualifier() = node1.asExpr()
)
}
/**
* Holds if `node1` to `node2` is a dataflow step that compiles a script via `MvelScriptEngine`
* by calling `engine.compile(tainted)` or `engine.compiledScript(tainted)`.
*/
private predicate scriptCompileStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma, Method m | ma.getMethod() = m |
m instanceof MvelScriptEngineCompilationMethod and
ma = node2.asExpr() and
ma.getArgument(0) = node1.asExpr()
)
}
/**
* Holds if `node1` to `node2` is a dataflow step that creates `MvelCompiledScript`
* by calling `new MvelCompiledScript(engine, tainted)`.
*/
private predicate createMvelCompiledScriptStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(ConstructorCall cc |
cc.getConstructedType() instanceof MvelCompiledScript and
cc = node2.asExpr() and
cc.getArgument(1) = node1.asExpr()
)
}
/**
* Holds if `node1` to `node2` is a dataflow step creates `TemplateCompiler`
* by calling `new TemplateCompiler(tainted)`.
*/
private predicate createTemplateCompilerStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(ConstructorCall cc |
cc.getConstructedType() instanceof TemplateCompiler and
cc = node2.asExpr() and
cc.getArgument(0) = node1.asExpr()
)
}
/**
* Holds if `node1` to `node2` is a dataflow step that compiles a script via `TemplateCompiler`
* by calling `compiler.compile()` or `TemplateCompiler.compileTemplate(tainted)`.
*/
private predicate templateCompileStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma, Method m | ma.getMethod() = m |
m instanceof TemplateCompilerCompileMethod and
ma.getQualifier() = node1.asExpr() and
ma = node2.asExpr()
)
or
exists(StaticMethodAccess ma, Method m | ma.getMethod() = m |
m instanceof TemplateCompilerCompileTemplateMethod and
ma = node2.asExpr() and
ma.getArgument(0) = node1.asExpr()
)
}
/**
* Methods in `MvelScriptEngine` that compile a MVEL expression.
*/
private class MvelScriptEngineCompilationMethod extends Method {
MvelScriptEngineCompilationMethod() {
getDeclaringType() instanceof MvelScriptEngine and
hasName(["compile", "compiledScript"])
}
}
/**
* `TemplateCompiler.compile()` method that compiles a MVEL template.
*/
private class TemplateCompilerCompileMethod extends Method {
TemplateCompilerCompileMethod() {
getDeclaringType() instanceof TemplateCompiler and
hasName("compile")
}
}
/**
* `TemplateCompiler.compileTemplate(tainted)` static method that compiles a MVEL template.
*/
private class TemplateCompilerCompileTemplateMethod extends Method {
TemplateCompilerCompileTemplateMethod() {
getDeclaringType() instanceof TemplateCompiler and
hasName("compileTemplate")
}
}
private class MVEL extends RefType {
MVEL() { hasQualifiedName("org.mvel2", "MVEL") }
}
private class ExpressionCompiler extends RefType {
ExpressionCompiler() { hasQualifiedName("org.mvel2.compiler", "ExpressionCompiler") }
}
private class CompiledAccExpression extends RefType {
CompiledAccExpression() { hasQualifiedName("org.mvel2.compiler", "CompiledAccExpression") }
}
private class MvelScriptEngine extends RefType {
MvelScriptEngine() { hasQualifiedName("org.mvel2.jsr223", "MvelScriptEngine") }
}
private class MvelCompiledScript extends RefType {
MvelCompiledScript() { hasQualifiedName("org.mvel2.jsr223", "MvelCompiledScript") }
}
private class TemplateCompiler extends RefType {
TemplateCompiler() { hasQualifiedName("org.mvel2.templates", "TemplateCompiler") }
}

View File

@@ -0,0 +1,26 @@
/** Provides taint tracking configurations to be used in MVEL injection related queries. */
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.MvelInjection
/**
* A taint-tracking configuration for unsafe user input
* that is used to construct and evaluate a MVEL expression.
*/
class MvelInjectionFlowConfig extends TaintTracking::Configuration {
MvelInjectionFlowConfig() { this = "MvelInjectionFlowConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof MvelEvaluationSink }
override predicate isSanitizer(DataFlow::Node sanitizer) {
sanitizer instanceof MvelInjectionSanitizer
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
any(MvelInjectionAdditionalTaintStep c).step(node1, node2)
}
}

View File

@@ -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()) }
}

View File

@@ -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()
)
}
}