mirror of
https://github.com/github/codeql.git
synced 2025-12-21 19:26:31 +01:00
Refactored SpelInjection.qll to use CSV sink models
This commit is contained in:
@@ -11,9 +11,25 @@
|
||||
*/
|
||||
|
||||
import java
|
||||
import SpelInjectionLib
|
||||
import semmle.code.java.security.SpelInjection
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, ExpressionInjectionConfig conf
|
||||
/**
|
||||
* A taint-tracking configuration for unsafe user input
|
||||
* that is used to construct and evaluate a SpEL expression.
|
||||
*/
|
||||
class SpELInjectionConfig extends TaintTracking::Configuration {
|
||||
SpELInjectionConfig() { this = "SpELInjectionConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof SpelExpressionEvaluationSink }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
any(SpelExpressionInjectionAdditionalTaintStep c).step(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, SpELInjectionConfig conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "SpEL injection from $@.", source.getNode(), "this user input"
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.dataflow.TaintTracking2
|
||||
import SpringFrameworkLib
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for unsafe user input
|
||||
* that is used to construct and evaluate a SpEL expression.
|
||||
*/
|
||||
class ExpressionInjectionConfig extends TaintTracking::Configuration {
|
||||
ExpressionInjectionConfig() { this = "ExpressionInjectionConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof ExpressionEvaluationSink }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
expressionParsingStep(node1, node2) or
|
||||
springPropertiesStep(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for SpEL injection vulnerabilities,
|
||||
* i.e. methods that run evaluation of a SpEL expression in a powerfull context.
|
||||
*/
|
||||
class ExpressionEvaluationSink extends DataFlow::ExprNode {
|
||||
ExpressionEvaluationSink() {
|
||||
exists(MethodAccess ma, Method m | m = ma.getMethod() |
|
||||
m instanceof ExpressionEvaluationMethod and
|
||||
getExpr() = ma.getQualifier() and
|
||||
not exists(SafeEvaluationContextFlowConfig config |
|
||||
config.hasFlowTo(DataFlow::exprNode(ma.getArgument(0)))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node1` to `node2` is a dataflow step that parses a SpEL expression,
|
||||
* i.e. `parser.parseExpression(tainted)`.
|
||||
*/
|
||||
predicate expressionParsingStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(MethodAccess ma, Method m | ma.getMethod() = m |
|
||||
m.getDeclaringType().getAnAncestor*() instanceof ExpressionParser and
|
||||
m.hasName("parseExpression") and
|
||||
ma.getAnArgument() = node1.asExpr() and
|
||||
node2.asExpr() = ma
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration for safe evaluation context that may be used in expression evaluation.
|
||||
*/
|
||||
class SafeEvaluationContextFlowConfig extends DataFlow2::Configuration {
|
||||
SafeEvaluationContextFlowConfig() { this = "SpelInjection::SafeEvaluationContextFlowConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof SafeContextSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(MethodAccess ma, Method m | m = ma.getMethod() |
|
||||
m instanceof ExpressionEvaluationMethod and
|
||||
ma.getArgument(0) = sink.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override int fieldFlowBranchLimit() { result = 0 }
|
||||
}
|
||||
|
||||
class SafeContextSource extends DataFlow::ExprNode {
|
||||
SafeContextSource() {
|
||||
isSimpleEvaluationContextConstructorCall(getExpr()) or
|
||||
isSimpleEvaluationContextBuilderCall(getExpr())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `expr` constructs `SimpleEvaluationContext`.
|
||||
*/
|
||||
predicate isSimpleEvaluationContextConstructorCall(Expr expr) {
|
||||
exists(ConstructorCall cc |
|
||||
cc.getConstructedType() instanceof SimpleEvaluationContext and
|
||||
cc = expr
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `expr` builds `SimpleEvaluationContext` via `SimpleEvaluationContext.Builder`,
|
||||
* e.g. `SimpleEvaluationContext.forReadWriteDataBinding().build()`.
|
||||
*/
|
||||
predicate isSimpleEvaluationContextBuilderCall(Expr expr) {
|
||||
exists(MethodAccess ma, Method m | ma.getMethod() = m |
|
||||
m.getDeclaringType() instanceof SimpleEvaluationContextBuilder and
|
||||
m.hasName("build") and
|
||||
ma = expr
|
||||
)
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
|
||||
/**
|
||||
* Methods that trigger evaluation of an expression.
|
||||
*/
|
||||
class ExpressionEvaluationMethod extends Method {
|
||||
ExpressionEvaluationMethod() {
|
||||
getDeclaringType() instanceof Expression and
|
||||
(
|
||||
hasName("getValue") or
|
||||
hasName("getValueTypeDescriptor") or
|
||||
hasName("getValueType") or
|
||||
hasName("setValue")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node1` to `node2` is a dataflow step that converts `PropertyValues`
|
||||
* to an array of `PropertyValue`, i.e. `tainted.getPropertyValues()`.
|
||||
*/
|
||||
predicate getPropertyValuesStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(MethodAccess ma, Method m | m = ma.getMethod() |
|
||||
node1.asExpr() = ma.getQualifier() and
|
||||
node2.asExpr() = ma and
|
||||
m.getDeclaringType() instanceof PropertyValues and
|
||||
m.hasName("getPropertyValues")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node1` to `node2` is a dataflow step that constructs `MutablePropertyValues`,
|
||||
* i.e. `new MutablePropertyValues(tainted)`.
|
||||
*/
|
||||
predicate createMutablePropertyValuesStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(ConstructorCall cc | cc.getConstructedType() instanceof MutablePropertyValues |
|
||||
node1.asExpr() = cc.getAnArgument() and
|
||||
node2.asExpr() = cc
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node1` to `node2` is a dataflow step that returns a name of `PropertyValue`,
|
||||
* i.e. `tainted.getName()`.
|
||||
*/
|
||||
predicate getPropertyNameStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(MethodAccess ma, Method m | m = ma.getMethod() |
|
||||
node1.asExpr() = ma.getQualifier() and
|
||||
node2.asExpr() = ma and
|
||||
m.getDeclaringType() instanceof PropertyValue and
|
||||
m.hasName("getName")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node1` to `node2` is a dataflow step that converts `MutablePropertyValues`
|
||||
* to a list of `PropertyValue`, i.e. `tainted.getPropertyValueList()`.
|
||||
*/
|
||||
predicate getPropertyValueListStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(MethodAccess ma, Method m | m = ma.getMethod() |
|
||||
node1.asExpr() = ma.getQualifier() and
|
||||
node2.asExpr() = ma and
|
||||
m.getDeclaringType() instanceof MutablePropertyValues and
|
||||
m.hasName("getPropertyValueList")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node1` to `node2` is one of the dataflow steps that propagate
|
||||
* tainted data via Spring properties.
|
||||
*/
|
||||
predicate springPropertiesStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
createMutablePropertyValuesStep(node1, node2) or
|
||||
getPropertyNameStep(node1, node2) or
|
||||
getPropertyValuesStep(node1, node2) or
|
||||
getPropertyValueListStep(node1, node2)
|
||||
}
|
||||
|
||||
class PropertyValue extends RefType {
|
||||
PropertyValue() { hasQualifiedName("org.springframework.beans", "PropertyValue") }
|
||||
}
|
||||
|
||||
class PropertyValues extends RefType {
|
||||
PropertyValues() { hasQualifiedName("org.springframework.beans", "PropertyValues") }
|
||||
}
|
||||
|
||||
class MutablePropertyValues extends RefType {
|
||||
MutablePropertyValues() { hasQualifiedName("org.springframework.beans", "MutablePropertyValues") }
|
||||
}
|
||||
|
||||
class SimpleEvaluationContext extends RefType {
|
||||
SimpleEvaluationContext() {
|
||||
hasQualifiedName("org.springframework.expression.spel.support", "SimpleEvaluationContext")
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleEvaluationContextBuilder extends RefType {
|
||||
SimpleEvaluationContextBuilder() {
|
||||
hasQualifiedName("org.springframework.expression.spel.support",
|
||||
"SimpleEvaluationContext$Builder")
|
||||
}
|
||||
}
|
||||
|
||||
class Expression extends RefType {
|
||||
Expression() { hasQualifiedName("org.springframework.expression", "Expression") }
|
||||
}
|
||||
|
||||
class ExpressionParser extends RefType {
|
||||
ExpressionParser() { hasQualifiedName("org.springframework.expression", "ExpressionParser") }
|
||||
}
|
||||
145
java/ql/src/semmle/code/java/security/SpelInjection.qll
Normal file
145
java/ql/src/semmle/code/java/security/SpelInjection.qll
Normal file
@@ -0,0 +1,145 @@
|
||||
/** Provides classes to reason about SpEL injection attacks. */
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
|
||||
/** A data flow sink for unvalidated user input that is used to construct SpEL expressions. */
|
||||
abstract class SpelExpressionEvaluationSink extends DataFlow::ExprNode { }
|
||||
|
||||
private class SpelExpressionEvaluationModel extends SinkModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
"org.springframework.expression;Expression;true;getValue;;;Argument[-1];spel",
|
||||
"org.springframework.expression;Expression;true;getValueTypeDescriptor;;;Argument[-1];spel",
|
||||
"org.springframework.expression;Expression;true;getValueType;;;Argument[-1];spel",
|
||||
"org.springframework.expression;Expression;true;setValue;;;Argument[-1];spel"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** Default sink for SpEL injection vulnerabilities. */
|
||||
private class DefaultSpelExpressionEvaluationSink extends SpelExpressionEvaluationSink {
|
||||
DefaultSpelExpressionEvaluationSink() {
|
||||
exists(MethodAccess ma |
|
||||
sinkNode(this, "spel") and
|
||||
this.asExpr() = ma.getQualifier() and
|
||||
not exists(SafeEvaluationContextFlowConfig config |
|
||||
config.hasFlowTo(DataFlow::exprNode(ma.getArgument(0)))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional taint steps.
|
||||
*
|
||||
* Extend this class to add additional taint steps that should apply to the `SpELInjectionConfig`.
|
||||
*/
|
||||
class SpelExpressionInjectionAdditionalTaintStep extends Unit {
|
||||
/**
|
||||
* Holds if the step from `node1` to `node2` should be considered a taint
|
||||
* step for the `SpELInjectionConfig` configuration.
|
||||
*/
|
||||
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
|
||||
}
|
||||
|
||||
/** A set of additional taint steps to consider when taint tracking SpEL related data flows. */
|
||||
private class DefaultSpelExpressionInjectionAdditionalTaintStep extends SpelExpressionInjectionAdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
expressionParsingStep(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration for safe evaluation context that may be used in expression evaluation.
|
||||
*/
|
||||
class SafeEvaluationContextFlowConfig extends DataFlow2::Configuration {
|
||||
SafeEvaluationContextFlowConfig() { this = "SpelInjection::SafeEvaluationContextFlowConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof SafeContextSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(MethodAccess ma |
|
||||
ma.getMethod() instanceof ExpressionEvaluationMethod and
|
||||
ma.getArgument(0) = sink.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override int fieldFlowBranchLimit() { result = 0 }
|
||||
}
|
||||
|
||||
private class SafeContextSource extends DataFlow::ExprNode {
|
||||
SafeContextSource() {
|
||||
isSimpleEvaluationContextConstructorCall(getExpr()) or
|
||||
isSimpleEvaluationContextBuilderCall(getExpr())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `expr` constructs `SimpleEvaluationContext`.
|
||||
*/
|
||||
private predicate isSimpleEvaluationContextConstructorCall(Expr expr) {
|
||||
exists(ConstructorCall cc |
|
||||
cc.getConstructedType() instanceof SimpleEvaluationContext and
|
||||
cc = expr
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `expr` builds `SimpleEvaluationContext` via `SimpleEvaluationContext.Builder`,
|
||||
* for instance, `SimpleEvaluationContext.forReadWriteDataBinding().build()`.
|
||||
*/
|
||||
private predicate isSimpleEvaluationContextBuilderCall(Expr expr) {
|
||||
exists(MethodAccess ma, Method m | ma.getMethod() = m |
|
||||
m.getDeclaringType() instanceof SimpleEvaluationContextBuilder and
|
||||
m.hasName("build") and
|
||||
ma = expr
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods that trigger evaluation of an expression.
|
||||
*/
|
||||
private class ExpressionEvaluationMethod extends Method {
|
||||
ExpressionEvaluationMethod() {
|
||||
this.getDeclaringType() instanceof Expression and
|
||||
this.hasName(["getValue", "getValueTypeDescriptor", "getValueType", "setValue"])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node1` to `node2` is a dataflow step that parses a SpEL expression,
|
||||
* by calling `parser.parseExpression(tainted)`.
|
||||
*/
|
||||
private predicate expressionParsingStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(MethodAccess ma, Method m | ma.getMethod() = m |
|
||||
m.getDeclaringType().getAnAncestor*() instanceof ExpressionParser and
|
||||
m.hasName("parseExpression") and
|
||||
ma.getAnArgument() = node1.asExpr() and
|
||||
node2.asExpr() = ma
|
||||
)
|
||||
}
|
||||
|
||||
private class SimpleEvaluationContext extends RefType {
|
||||
SimpleEvaluationContext() {
|
||||
hasQualifiedName("org.springframework.expression.spel.support", "SimpleEvaluationContext")
|
||||
}
|
||||
}
|
||||
|
||||
private class SimpleEvaluationContextBuilder extends RefType {
|
||||
SimpleEvaluationContextBuilder() {
|
||||
hasQualifiedName("org.springframework.expression.spel.support",
|
||||
"SimpleEvaluationContext$Builder")
|
||||
}
|
||||
}
|
||||
|
||||
private class Expression extends RefType {
|
||||
Expression() { hasQualifiedName("org.springframework.expression", "Expression") }
|
||||
}
|
||||
|
||||
private class ExpressionParser extends RefType {
|
||||
ExpressionParser() { hasQualifiedName("org.springframework.expression", "ExpressionParser") }
|
||||
}
|
||||
Reference in New Issue
Block a user