mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +01:00
Moved from experimental
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
public Object evaluate(Socket socket) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(socket.getInputStream()))) {
|
||||
|
||||
String string = reader.readLine();
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
Expression expression = parser.parseExpression(string);
|
||||
SimpleEvaluationContext context
|
||||
= SimpleEvaluationContext.forReadWriteDataBinding().build();
|
||||
return expression.getValue(context);
|
||||
}
|
||||
}
|
||||
56
java/ql/src/Security/CWE/CWE-094/SpelInjection.qhelp
Normal file
56
java/ql/src/Security/CWE/CWE-094/SpelInjection.qhelp
Normal file
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
The Spring Expression Language (SpEL) is a powerful expression language
|
||||
provided by Spring Framework. The language offers many features
|
||||
including invocation of methods available in the JVM.
|
||||
If a SpEL expression is built using attacker-controlled data,
|
||||
and then evaluated in a powerful context,
|
||||
then it may allow the attacker to run arbitrary code.
|
||||
</p>
|
||||
<p>
|
||||
The <code>SpelExpressionParser</code> class parses a SpEL expression string
|
||||
and returns an <code>Expression</code> instance
|
||||
that can be then evaluated by calling one of its methods.
|
||||
By default, an expression is evaluated in a powerful <code>StandardEvaluationContext</code>
|
||||
that allows the expression to access other methods available in the JVM.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
In general, including user input in a SpEL expression should be avoided.
|
||||
If user input must be included in the expression,
|
||||
it should be then evaluated in a limited context
|
||||
that doesn't allow arbitrary method invocation.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example uses untrusted data to build a SpEL expression
|
||||
and then runs it in the default powerfull context.
|
||||
</p>
|
||||
<sample src="UnsafeSpelExpressionEvaluation.java" />
|
||||
|
||||
<p>
|
||||
The next example shows how an untrusted SpEL expression can be run
|
||||
in <code>SimpleEvaluationContext</code> that doesn't allow accessing arbitrary methods.
|
||||
However, it's recommended to avoid using untrusted input in SpEL expressions.
|
||||
</p>
|
||||
<sample src="SaferSpelExpressionEvaluation.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Spring Framework Reference Documentation:
|
||||
<a href="https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/expressions.html">Spring Expression Language (SpEL)</a>.
|
||||
</li>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection">Expression Language Injection</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
19
java/ql/src/Security/CWE/CWE-094/SpelInjection.ql
Normal file
19
java/ql/src/Security/CWE/CWE-094/SpelInjection.ql
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @name Expression language injection (Spring)
|
||||
* @description Evaluation of a user-controlled Spring Expression Language (SpEL) expression
|
||||
* may lead to remote code execution.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/spel-expression-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import java
|
||||
import SpelInjectionLib
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, ExpressionInjectionConfig conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "SpEL injection from $@.", source.getNode(), "this user input"
|
||||
97
java/ql/src/Security/CWE/CWE-094/SpelInjectionLib.qll
Normal file
97
java/ql/src/Security/CWE/CWE-094/SpelInjectionLib.qll
Normal file
@@ -0,0 +1,97 @@
|
||||
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
|
||||
)
|
||||
}
|
||||
111
java/ql/src/Security/CWE/CWE-094/SpringFrameworkLib.qll
Normal file
111
java/ql/src/Security/CWE/CWE-094/SpringFrameworkLib.qll
Normal file
@@ -0,0 +1,111 @@
|
||||
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") }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
public Object evaluate(Socket socket) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(socket.getInputStream()))) {
|
||||
|
||||
String string = reader.readLine();
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
Expression expression = parser.parseExpression(string);
|
||||
return expression.getValue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user