Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 changes: 56 additions & 0 deletions java/ql/src/experimental/Security/CWE/CWE-094/SpelInjection.qhelp
Original file line number Diff line number Diff line change
@@ -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 changes: 19 additions & 0 deletions java/ql/src/experimental/Security/CWE/CWE-094/SpelInjection.ql
Original file line number Diff line number Diff line change
@@ -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"
100 changes: 100 additions & 0 deletions java/ql/src/experimental/Security/CWE/CWE-094/SpelInjectionLib.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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 or
source instanceof WebRequestSource
}

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
)
}
136 changes: 136 additions & 0 deletions java/ql/src/experimental/Security/CWE/CWE-094/SpringFrameworkLib.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
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")
)
}
}

/**
* `WebRequest` interface is a source of tainted data.
*/
class WebRequestSource extends DataFlow::Node {
WebRequestSource() {
exists(MethodAccess ma, Method m | ma.getMethod() = m |
m.getDeclaringType() instanceof WebRequest and
(
m.hasName("getHeader") or
m.hasName("getHeaderValues") or
m.hasName("getHeaderNames") or
m.hasName("getParameter") or
m.hasName("getParameterValues") or
m.hasName("getParameterNames") or
m.hasName("getParameterMap")
) and
ma = asExpr()
)
}
}

/**
* 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 WebRequest extends RefType {
WebRequest() { hasQualifiedName("org.springframework.web.context.request", "WebRequest") }
}

class Expression extends RefType {
Expression() { hasQualifiedName("org.springframework.expression", "Expression") }
}

class ExpressionParser extends RefType {
ExpressionParser() { hasQualifiedName("org.springframework.expression", "ExpressionParser") }
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
edges
| SpelInjection.java:15:22:15:44 | getInputStream(...) : InputStream | SpelInjection.java:23:5:23:14 | expression |
| SpelInjection.java:27:22:27:44 | getInputStream(...) : InputStream | SpelInjection.java:34:5:34:14 | expression |
| SpelInjection.java:38:22:38:44 | getInputStream(...) : InputStream | SpelInjection.java:48:5:48:14 | expression |
| SpelInjection.java:52:22:52:44 | getInputStream(...) : InputStream | SpelInjection.java:59:5:59:14 | expression |
| SpelInjection.java:63:22:63:44 | getInputStream(...) : InputStream | SpelInjection.java:70:5:70:14 | expression |
| SpelInjection.java:74:22:74:44 | getInputStream(...) : InputStream | SpelInjection.java:83:5:83:14 | expression |
nodes
| SpelInjection.java:15:22:15:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SpelInjection.java:23:5:23:14 | expression | semmle.label | expression |
| SpelInjection.java:27:22:27:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SpelInjection.java:34:5:34:14 | expression | semmle.label | expression |
| SpelInjection.java:38:22:38:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SpelInjection.java:48:5:48:14 | expression | semmle.label | expression |
| SpelInjection.java:52:22:52:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SpelInjection.java:59:5:59:14 | expression | semmle.label | expression |
| SpelInjection.java:63:22:63:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SpelInjection.java:70:5:70:14 | expression | semmle.label | expression |
| SpelInjection.java:74:22:74:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SpelInjection.java:83:5:83:14 | expression | semmle.label | expression |
#select
| SpelInjection.java:23:5:23:14 | expression | SpelInjection.java:15:22:15:44 | getInputStream(...) : InputStream | SpelInjection.java:23:5:23:14 | expression | SpEL injection from $@. | SpelInjection.java:15:22:15:44 | getInputStream(...) | this user input |
| SpelInjection.java:34:5:34:14 | expression | SpelInjection.java:27:22:27:44 | getInputStream(...) : InputStream | SpelInjection.java:34:5:34:14 | expression | SpEL injection from $@. | SpelInjection.java:27:22:27:44 | getInputStream(...) | this user input |
| SpelInjection.java:48:5:48:14 | expression | SpelInjection.java:38:22:38:44 | getInputStream(...) : InputStream | SpelInjection.java:48:5:48:14 | expression | SpEL injection from $@. | SpelInjection.java:38:22:38:44 | getInputStream(...) | this user input |
| SpelInjection.java:59:5:59:14 | expression | SpelInjection.java:52:22:52:44 | getInputStream(...) : InputStream | SpelInjection.java:59:5:59:14 | expression | SpEL injection from $@. | SpelInjection.java:52:22:52:44 | getInputStream(...) | this user input |
| SpelInjection.java:70:5:70:14 | expression | SpelInjection.java:63:22:63:44 | getInputStream(...) : InputStream | SpelInjection.java:70:5:70:14 | expression | SpEL injection from $@. | SpelInjection.java:63:22:63:44 | getInputStream(...) | this user input |
| SpelInjection.java:83:5:83:14 | expression | SpelInjection.java:74:22:74:44 | getInputStream(...) : InputStream | SpelInjection.java:83:5:83:14 | expression | SpEL injection from $@. | SpelInjection.java:74:22:74:44 | getInputStream(...) | this user input |
Loading