mirror of
https://github.com/github/codeql.git
synced 2026-02-23 18:33:42 +01:00
Merge branch 'main' into atorralba/promote-groovy-injection
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -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(..)`.
|
||||
*/
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
174
java/ql/src/semmle/code/java/frameworks/Jackson.qll
Normal file
174
java/ql/src/semmle/code/java/frameworks/Jackson.qll
Normal 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()
|
||||
)
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
233
java/ql/src/semmle/code/java/security/MvelInjection.qll
Normal file
233
java/ql/src/semmle/code/java/security/MvelInjection.qll
Normal 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") }
|
||||
}
|
||||
26
java/ql/src/semmle/code/java/security/MvelInjectionQuery.qll
Normal file
26
java/ql/src/semmle/code/java/security/MvelInjectionQuery.qll
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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()) }
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user