mirror of
https://github.com/github/codeql.git
synced 2026-02-09 19:51:07 +01:00
Merge branch 'main' into atorralba/promote-groovy-injection
This commit is contained in:
@@ -1,38 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
MVEL is an expression language based on Java-syntax.
|
||||
The language offers many features
|
||||
including invocation of methods available in the JVM.
|
||||
If a MVEL expression is built using attacker-controlled data,
|
||||
and then evaluated, then it may allow the attacker to run arbitrary code.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Including user input in a MVEL expression should be avoided.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example uses untrusted data to build a MVEL expression
|
||||
and then runs it in the default powerfull context.
|
||||
</p>
|
||||
<sample src="UnsafeMvelExpressionEvaluation.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
MVEL Documentation:
|
||||
<a href="http://mvel.documentnode.com/">Language Guide for 2.0</a>.
|
||||
</li>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection">Expression Language Injection</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* @name Expression language injection (MVEL)
|
||||
* @description Evaluation of a user-controlled MVEL expression
|
||||
* may lead to remote code execution.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/mvel-expression-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import java
|
||||
import MvelInjectionLib
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, MvelInjectionConfig conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "MVEL injection from $@.", source.getNode(), "this user input"
|
||||
@@ -1,367 +0,0 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for unsafe user input
|
||||
* that is used to construct and evaluate a MVEL expression.
|
||||
*/
|
||||
class MvelInjectionConfig extends TaintTracking::Configuration {
|
||||
MvelInjectionConfig() { this = "MvelInjectionConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof MvelEvaluationSink }
|
||||
|
||||
override predicate isAdditionalTaintStep(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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for EL injection vulnerabilities via MVEL,
|
||||
* i.e. methods that run evaluation of a MVEL expression.
|
||||
*/
|
||||
class MvelEvaluationSink extends DataFlow::ExprNode {
|
||||
MvelEvaluationSink() {
|
||||
exists(StaticMethodAccess ma, Method m | m = ma.getMethod() |
|
||||
(
|
||||
m instanceof MvelEvalMethod or
|
||||
m instanceof TemplateRuntimeEvaluationMethod
|
||||
) and
|
||||
ma.getArgument(0) = asExpr()
|
||||
)
|
||||
or
|
||||
exists(MethodAccess ma, Method m | m = ma.getMethod() |
|
||||
m instanceof MvelScriptEngineEvaluationMethod and
|
||||
ma.getArgument(0) = asExpr()
|
||||
)
|
||||
or
|
||||
exists(MethodAccess ma, Method m | m = ma.getMethod() |
|
||||
(
|
||||
m instanceof ExecutableStatementEvaluationMethod or
|
||||
m instanceof CompiledExpressionEvaluationMethod or
|
||||
m instanceof CompiledAccExpressionEvaluationMethod or
|
||||
m instanceof AccessorEvaluationMethod or
|
||||
m instanceof CompiledScriptEvaluationMethod or
|
||||
m instanceof MvelCompiledScriptEvaluationMethod
|
||||
) and
|
||||
ma.getQualifier() = asExpr()
|
||||
)
|
||||
or
|
||||
exists(StaticMethodAccess ma, Method m | m = ma.getMethod() |
|
||||
m instanceof MvelRuntimeEvaluationMethod and
|
||||
ma.getArgument(1) = asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node1` to `node2` is a dataflow step that compiles a MVEL expression
|
||||
* by callilng `MVEL.compileExpression(tainted)`.
|
||||
*/
|
||||
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 creates `ExpressionCompiler`,
|
||||
* i.e. `new ExpressionCompiler(tainted)`.
|
||||
*/
|
||||
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`,
|
||||
* i.e. `new CompiledAccExpression(tainted, ...)`.
|
||||
*/
|
||||
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()`.
|
||||
*/
|
||||
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`,
|
||||
* i.e. `engine.compile(tainted)` or `engine.compiledScript(tainted)`.
|
||||
*/
|
||||
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 creates `MvelCompiledScript`,
|
||||
* i.e. `new MvelCompiledScript(engine, tainted)`.
|
||||
*/
|
||||
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`,
|
||||
* i.e. `new TemplateCompiler(tainted)`.
|
||||
*/
|
||||
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`,
|
||||
* i.e. `compiler.compile()` or `TemplateCompiler.compileTemplate(tainted)`.
|
||||
*/
|
||||
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 the MVEL class that evaluate a MVEL expression.
|
||||
*/
|
||||
class MvelEvalMethod extends Method {
|
||||
MvelEvalMethod() {
|
||||
getDeclaringType() instanceof MVEL and
|
||||
(
|
||||
hasName("eval") or
|
||||
hasName("executeExpression") or
|
||||
hasName("evalToBoolean") or
|
||||
hasName("evalToString") or
|
||||
hasName("executeAllExpression") or
|
||||
hasName("executeSetExpression")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in `MVEL` class that compile a MVEL expression.
|
||||
*/
|
||||
class MvelCompileExpressionMethod extends Method {
|
||||
MvelCompileExpressionMethod() {
|
||||
getDeclaringType() instanceof MVEL and
|
||||
(
|
||||
hasName("compileExpression") or
|
||||
hasName("compileGetExpression") or
|
||||
hasName("compileSetExpression")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in `ExecutableStatement` that evaluate a MVEL expression.
|
||||
*/
|
||||
class ExecutableStatementEvaluationMethod extends Method {
|
||||
ExecutableStatementEvaluationMethod() {
|
||||
getDeclaringType() instanceof ExecutableStatement and
|
||||
hasName("getValue")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in `CompiledExpression` that evaluate a MVEL expression.
|
||||
*/
|
||||
class CompiledExpressionEvaluationMethod extends Method {
|
||||
CompiledExpressionEvaluationMethod() {
|
||||
getDeclaringType() instanceof CompiledExpression and
|
||||
hasName("getDirectValue")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in `CompiledAccExpression` that evaluate a MVEL expression.
|
||||
*/
|
||||
class CompiledAccExpressionEvaluationMethod extends Method {
|
||||
CompiledAccExpressionEvaluationMethod() {
|
||||
getDeclaringType() instanceof CompiledAccExpression and
|
||||
hasName("getValue")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in `Accessor` that evaluate a MVEL expression.
|
||||
*/
|
||||
class AccessorEvaluationMethod extends Method {
|
||||
AccessorEvaluationMethod() {
|
||||
getDeclaringType() instanceof Accessor and
|
||||
hasName("getValue")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in `MvelScriptEngine` that evaluate a MVEL expression.
|
||||
*/
|
||||
class MvelScriptEngineEvaluationMethod extends Method {
|
||||
MvelScriptEngineEvaluationMethod() {
|
||||
getDeclaringType() instanceof MvelScriptEngine and
|
||||
(hasName("eval") or hasName("evaluate"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in `MvelScriptEngine` that compile a MVEL expression.
|
||||
*/
|
||||
class MvelScriptEngineCompilationMethod extends Method {
|
||||
MvelScriptEngineCompilationMethod() {
|
||||
getDeclaringType() instanceof MvelScriptEngine and
|
||||
(hasName("compile") or hasName("compiledScript"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in `CompiledScript` that evaluate a MVEL expression.
|
||||
*/
|
||||
class CompiledScriptEvaluationMethod extends Method {
|
||||
CompiledScriptEvaluationMethod() {
|
||||
getDeclaringType() instanceof CompiledScript and
|
||||
hasName("eval")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in `TemplateRuntime` that evaluate a MVEL template.
|
||||
*/
|
||||
class TemplateRuntimeEvaluationMethod extends Method {
|
||||
TemplateRuntimeEvaluationMethod() {
|
||||
getDeclaringType() instanceof TemplateRuntime and
|
||||
(hasName("eval") or hasName("execute"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `TemplateCompiler.compile()` method compiles a MVEL template.
|
||||
*/
|
||||
class TemplateCompilerCompileMethod extends Method {
|
||||
TemplateCompilerCompileMethod() {
|
||||
getDeclaringType() instanceof TemplateCompiler and
|
||||
hasName("compile")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `TemplateCompiler.compileTemplate(tainted)` static method compiles a MVEL template.
|
||||
*/
|
||||
class TemplateCompilerCompileTemplateMethod extends Method {
|
||||
TemplateCompilerCompileTemplateMethod() {
|
||||
getDeclaringType() instanceof TemplateCompiler and
|
||||
hasName("compileTemplate")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in `MvelCompiledScript` that evaluate a MVEL expression.
|
||||
*/
|
||||
class MvelCompiledScriptEvaluationMethod extends Method {
|
||||
MvelCompiledScriptEvaluationMethod() {
|
||||
getDeclaringType() instanceof MvelCompiledScript and
|
||||
hasName("eval")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in `MVELRuntime` that evaluate a MVEL expression.
|
||||
*/
|
||||
class MvelRuntimeEvaluationMethod extends Method {
|
||||
MvelRuntimeEvaluationMethod() {
|
||||
getDeclaringType() instanceof MVELRuntime and
|
||||
hasName("execute")
|
||||
}
|
||||
}
|
||||
|
||||
class MVEL extends RefType {
|
||||
MVEL() { hasQualifiedName("org.mvel2", "MVEL") }
|
||||
}
|
||||
|
||||
class ExpressionCompiler extends RefType {
|
||||
ExpressionCompiler() { hasQualifiedName("org.mvel2.compiler", "ExpressionCompiler") }
|
||||
}
|
||||
|
||||
class ExecutableStatement extends RefType {
|
||||
ExecutableStatement() { hasQualifiedName("org.mvel2.compiler", "ExecutableStatement") }
|
||||
}
|
||||
|
||||
class CompiledExpression extends RefType {
|
||||
CompiledExpression() { hasQualifiedName("org.mvel2.compiler", "CompiledExpression") }
|
||||
}
|
||||
|
||||
class CompiledAccExpression extends RefType {
|
||||
CompiledAccExpression() { hasQualifiedName("org.mvel2.compiler", "CompiledAccExpression") }
|
||||
}
|
||||
|
||||
class Accessor extends RefType {
|
||||
Accessor() { hasQualifiedName("org.mvel2.compiler", "Accessor") }
|
||||
}
|
||||
|
||||
class CompiledScript extends RefType {
|
||||
CompiledScript() { hasQualifiedName("javax.script", "CompiledScript") }
|
||||
}
|
||||
|
||||
class MvelScriptEngine extends RefType {
|
||||
MvelScriptEngine() { hasQualifiedName("org.mvel2.jsr223", "MvelScriptEngine") }
|
||||
}
|
||||
|
||||
class MvelCompiledScript extends RefType {
|
||||
MvelCompiledScript() { hasQualifiedName("org.mvel2.jsr223", "MvelCompiledScript") }
|
||||
}
|
||||
|
||||
class TemplateRuntime extends RefType {
|
||||
TemplateRuntime() { hasQualifiedName("org.mvel2.templates", "TemplateRuntime") }
|
||||
}
|
||||
|
||||
class TemplateCompiler extends RefType {
|
||||
TemplateCompiler() { hasQualifiedName("org.mvel2.templates", "TemplateCompiler") }
|
||||
}
|
||||
|
||||
class MVELRuntime extends RefType {
|
||||
MVELRuntime() { hasQualifiedName("org.mvel2", "MVELRuntime") }
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
public void evaluate(Socket socket) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(socket.getInputStream()))) {
|
||||
|
||||
String expression = reader.readLine();
|
||||
MVEL.eval(expression);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ all certificate errors are ignored. In the 'GOOD' case, certificate errors are r
|
||||
|
||||
<references>
|
||||
<li>Teamdev:
|
||||
<a href="https://jxbrowser.support.teamdev.com/support/discussions/topics/9000051708">
|
||||
<a href="https://jxbrowser-support.teamdev.com/release-notes/2019/v6-24.html">
|
||||
Changelog of JxBrowser 6.24</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Controller
|
||||
public class UnsafeReflection {
|
||||
|
||||
@RequestMapping(value = {"/service/{beanIdOrClassName}/{methodName}"}, method = {RequestMethod.POST}, consumes = {"application/json"}, produces = {"application/json"})
|
||||
public Object bad1(@PathVariable("beanIdOrClassName") String beanIdOrClassName, @PathVariable("methodName") String methodName, @RequestBody Map<String, Object> body) throws Exception {
|
||||
List<Object> rawData = null;
|
||||
try {
|
||||
rawData = (List<Object>)body.get("methodInput");
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
}
|
||||
return invokeService(beanIdOrClassName, methodName, null, rawData);
|
||||
}
|
||||
|
||||
@GetMapping(value = "uf1")
|
||||
public void good1(HttpServletRequest request) throws Exception {
|
||||
HashSet<String> hashSet = new HashSet<>();
|
||||
hashSet.add("com.example.test1");
|
||||
hashSet.add("com.example.test2");
|
||||
String className = request.getParameter("className");
|
||||
String parameterValue = request.getParameter("parameterValue");
|
||||
if (!hashSet.contains(className)){
|
||||
throw new Exception("Class not valid: " + className);
|
||||
}
|
||||
try {
|
||||
Class clazz = Class.forName(className);
|
||||
Object object = clazz.getDeclaredConstructors()[0].newInstance(parameterValue); //good
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping(value = "uf2")
|
||||
public void good2(HttpServletRequest request) throws Exception {
|
||||
String className = request.getParameter("className");
|
||||
String parameterValue = request.getParameter("parameterValue");
|
||||
if (!"com.example.test1".equals(className)){
|
||||
throw new Exception("Class not valid: " + className);
|
||||
}
|
||||
try {
|
||||
Class clazz = Class.forName(className);
|
||||
Object object = clazz.getDeclaredConstructors()[0].newInstance(parameterValue); //good
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private Object invokeService(String beanIdOrClassName, String methodName, MultipartFile[] files, List<Object> data) throws Exception {
|
||||
BeanFactory beanFactory = new BeanFactory();
|
||||
try {
|
||||
Object bean = null;
|
||||
Class<?> beanClass = Class.forName(beanIdOrClassName);
|
||||
bean = beanFactory.getBean(beanClass);
|
||||
byte b;
|
||||
int i;
|
||||
Method[] arrayOfMethod;
|
||||
for (i = (arrayOfMethod = bean.getClass().getMethods()).length, b = 0; b < i; ) {
|
||||
Method method = arrayOfMethod[b];
|
||||
if (!method.getName().equals(methodName)) {
|
||||
b++;
|
||||
continue;
|
||||
}
|
||||
Object result = method.invoke(bean, data);
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
return map;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class BeanFactory {
|
||||
|
||||
private static HashMap<String, Object> classNameMap = new HashMap<>();
|
||||
|
||||
private static HashMap<Class<?>, Object> classMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
classNameMap.put("xxxx", Runtime.getRuntime());
|
||||
classMap.put(Runtime.class, Runtime.getRuntime());
|
||||
}
|
||||
|
||||
public Object getBean(Class<?> clzz) {
|
||||
return classMap.get(clzz);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Allowing users to freely choose the name of a class to instantiate could provide means to attack a vulnerable appplication.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Create a list of classes that are allowed to load reflectively and strictly verify the input to ensure that
|
||||
users can only instantiate classes or execute methods that ought to be allowed.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The <code>bad</code> method shown below illustrate class loading with <code>Class.forName</code> without any check on the particular class being instantiated.
|
||||
The <code>good</code> methods illustrate some different ways to restrict which classes can be instantiated.
|
||||
</p>
|
||||
<sample src="UnsafeReflection.java" />
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
|
||||
<li>
|
||||
Unsafe use of Reflection | OWASP:
|
||||
<a href="https://owasp.org/www-community/vulnerabilities/Unsafe_use_of_Reflection">Unsafe use of Reflection</a>.
|
||||
</li>
|
||||
<li>
|
||||
Java owasp: Classes should not be loaded dynamically:
|
||||
<a href="https://rules.sonarsource.com/java/tag/owasp/RSPEC-2658">Classes should not be loaded dynamically</a>.
|
||||
</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @name Use of externally-controlled input to select classes or code ('unsafe reflection')
|
||||
* @description Use external input with reflection function to select the class or code to
|
||||
* be used, which brings serious security risks.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/unsafe-reflection
|
||||
* @tags security
|
||||
* external/cwe/cwe-470
|
||||
*/
|
||||
|
||||
import java
|
||||
import DataFlow
|
||||
import UnsafeReflectionLib
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import DataFlow::PathGraph
|
||||
|
||||
private class ContainsSanitizer extends DataFlow::BarrierGuard {
|
||||
ContainsSanitizer() { this.(MethodAccess).getMethod().hasName("contains") }
|
||||
|
||||
override predicate checks(Expr e, boolean branch) {
|
||||
e = this.(MethodAccess).getArgument(0) and branch = true
|
||||
}
|
||||
}
|
||||
|
||||
private class EqualsSanitizer extends DataFlow::BarrierGuard {
|
||||
EqualsSanitizer() { this.(MethodAccess).getMethod().hasName("equals") }
|
||||
|
||||
override predicate checks(Expr e, boolean branch) {
|
||||
e = [this.(MethodAccess).getArgument(0), this.(MethodAccess).getQualifier()] and
|
||||
branch = true
|
||||
}
|
||||
}
|
||||
|
||||
class UnsafeReflectionConfig extends TaintTracking::Configuration {
|
||||
UnsafeReflectionConfig() { this = "UnsafeReflectionConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeReflectionSink }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// Argument -> return of Class.forName, ClassLoader.loadClass
|
||||
exists(ReflectiveClassIdentifierMethodAccess rcimac |
|
||||
rcimac.getArgument(0) = pred.asExpr() and rcimac = succ.asExpr()
|
||||
)
|
||||
or
|
||||
// Qualifier -> return of Class.getDeclaredConstructors/Methods and similar
|
||||
exists(MethodAccess ma |
|
||||
(
|
||||
ma instanceof ReflectiveConstructorsAccess or
|
||||
ma instanceof ReflectiveMethodsAccess
|
||||
) and
|
||||
ma.getQualifier() = pred.asExpr() and
|
||||
ma = succ.asExpr()
|
||||
)
|
||||
or
|
||||
// Qualifier -> return of Object.getClass
|
||||
exists(MethodAccess ma |
|
||||
ma.getMethod().hasName("getClass") and
|
||||
ma.getMethod().getDeclaringType().hasQualifiedName("java.lang", "Object") and
|
||||
ma.getQualifier() = pred.asExpr() and
|
||||
ma = succ.asExpr()
|
||||
)
|
||||
or
|
||||
// Argument -> return of methods that look like Class.forName
|
||||
looksLikeResolveClassStep(pred, succ)
|
||||
or
|
||||
// Argument -> return of methods that look like `Object getInstance(Class c)`
|
||||
looksLikeInstantiateClassStep(pred, succ)
|
||||
or
|
||||
// Qualifier -> return of Constructor.newInstance, Class.newInstance
|
||||
exists(NewInstance ni |
|
||||
ni.getQualifier() = pred.asExpr() and
|
||||
ni = succ.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof ContainsSanitizer or guard instanceof EqualsSanitizer
|
||||
}
|
||||
}
|
||||
|
||||
private Expr getAMethodArgument(MethodAccess reflectiveCall) {
|
||||
result = reflectiveCall.(NewInstance).getAnArgument()
|
||||
or
|
||||
result = reflectiveCall.(MethodInvokeCall).getAnArgument()
|
||||
}
|
||||
|
||||
from
|
||||
DataFlow::PathNode source, DataFlow::PathNode sink, UnsafeReflectionConfig conf,
|
||||
MethodAccess reflectiveCall
|
||||
where
|
||||
conf.hasFlowPath(source, sink) and
|
||||
sink.getNode().asExpr() = reflectiveCall.getQualifier() and
|
||||
conf.hasFlowToExpr(getAMethodArgument(reflectiveCall))
|
||||
select sink.getNode(), source, sink, "Unsafe reflection of $@.", source.getNode(), "user input"
|
||||
@@ -0,0 +1,60 @@
|
||||
import java
|
||||
import DataFlow
|
||||
import semmle.code.java.Reflection
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
|
||||
/**
|
||||
* A call to `java.lang.reflect.Method.invoke`.
|
||||
*/
|
||||
class MethodInvokeCall extends MethodAccess {
|
||||
MethodInvokeCall() { this.getMethod().hasQualifiedName("java.lang.reflect", "Method", "invoke") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsafe reflection sink (the qualifier or method arguments to `Constructor.newInstance(...)` or `Method.invoke(...)`)
|
||||
*/
|
||||
class UnsafeReflectionSink extends DataFlow::ExprNode {
|
||||
UnsafeReflectionSink() {
|
||||
exists(MethodAccess ma |
|
||||
(
|
||||
ma.getMethod().hasQualifiedName("java.lang.reflect", "Constructor<>", "newInstance") or
|
||||
ma instanceof MethodInvokeCall
|
||||
) and
|
||||
this.asExpr() = [ma.getQualifier(), ma.getAnArgument()]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Class
|
||||
* and its name contains "resolve", "load", etc.
|
||||
*/
|
||||
predicate looksLikeResolveClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
exists(MethodAccess ma, Method m, int i, Expr arg |
|
||||
m = ma.getMethod() and arg = ma.getArgument(i)
|
||||
|
|
||||
m.getReturnType() instanceof TypeClass and
|
||||
m.getName().toLowerCase().regexpMatch("resolve|load|class|type") and
|
||||
arg.getType() instanceof TypeString and
|
||||
arg = fromNode.asExpr() and
|
||||
ma = toNode.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fromNode` to `toNode` is a dataflow step that looks like instantiating a class.
|
||||
* A method probably instantiates a class if it is external, takes a Class, returns an Object
|
||||
* and its name contains "instantiate" or similar terms.
|
||||
*/
|
||||
predicate looksLikeInstantiateClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
exists(MethodAccess ma, Method m, int i, Expr arg |
|
||||
m = ma.getMethod() and arg = ma.getArgument(i)
|
||||
|
|
||||
m.getReturnType() instanceof TypeObject and
|
||||
m.getName().toLowerCase().regexpMatch("instantiate|instance|create|make|getbean") and
|
||||
arg.getType() instanceof TypeClass and
|
||||
arg = fromNode.asExpr() and
|
||||
ma = toNode.asExpr()
|
||||
)
|
||||
}
|
||||
@@ -34,6 +34,29 @@ public class SpringUrlRedirect {
|
||||
}
|
||||
|
||||
@GetMapping("url5")
|
||||
public ResponseEntity<Void> bad5(String redirectUrl) {
|
||||
return ResponseEntity.status(HttpStatus.FOUND)
|
||||
.location(URI.create(redirectUrl))
|
||||
.build();
|
||||
}
|
||||
|
||||
@GetMapping("url6")
|
||||
public ResponseEntity<Void> bad6(String redirectUrl) {
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.setLocation(URI.create(redirectUrl));
|
||||
|
||||
return new ResponseEntity<>(httpHeaders, HttpStatus.SEE_OTHER);
|
||||
}
|
||||
|
||||
@GetMapping("url7")
|
||||
public ResponseEntity<Void> bad7(String redirectUrl) {
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.add("Location", redirectUrl);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.SEE_OTHER).headers(httpHeaders).build();
|
||||
}
|
||||
|
||||
@GetMapping("url8")
|
||||
public RedirectView good1(String redirectUrl) {
|
||||
RedirectView rv = new RedirectView();
|
||||
if (redirectUrl.startsWith(VALID_REDIRECT)){
|
||||
|
||||
@@ -21,10 +21,10 @@ redirects on the server; then choose from that list based on the user input prov
|
||||
<example>
|
||||
|
||||
<p>The following examples show the bad case and the good case respectively.
|
||||
In <code>bad1</code> method and <code>bad2</code> method and <code>bad3</code> method and
|
||||
<code>bad4</code> method, shows an HTTP request parameter being used directly in a URL redirect
|
||||
without validating the input, which facilitates phishing attacks. In <code>good1</code> method,
|
||||
shows how to solve this problem by verifying whether the user input is a known fixed string beginning.
|
||||
The <code>bad</code> methods show an HTTP request parameter being used directly
|
||||
in a URL redirect without validating the input, which facilitates phishing attacks.
|
||||
In the <code>good1</code> method, it is shown how to solve this problem by verifying whether
|
||||
the user input is a known fixed string beginning.
|
||||
</p>
|
||||
|
||||
<sample src="SpringUrlRedirect.java" />
|
||||
@@ -33,5 +33,6 @@ shows how to solve this problem by verifying whether the user input is a known f
|
||||
<references>
|
||||
<li>A Guide To Spring Redirects: <a href="https://www.baeldung.com/spring-redirect-and-forward">Spring Redirects</a>.</li>
|
||||
<li>Url redirection - attack and defense: <a href="https://www.virtuesecurity.com/kb/url-redirection-attack-and-defense/">Url Redirection</a>.</li>
|
||||
<li>How to redirect to an external URL from Spring Boot REST Controller (Post/Redirect/Get pattern)?: <a href="https://fullstackdeveloper.guru/2021/03/12/how-to-redirect-to-an-external-url-from-spring-boot-rest-controller/">ResponseEntity Redirection</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -34,6 +34,10 @@ class SpringUrlRedirectFlowConfig extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof SpringUrlRedirectSink }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
springUrlRedirectTaintStep(fromNode, toNode)
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof StartsWithSanitizer
|
||||
}
|
||||
@@ -57,6 +61,8 @@ class SpringUrlRedirectFlowConfig extends TaintTracking::Configuration {
|
||||
not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().regexpMatch("^%s.*")
|
||||
)
|
||||
)
|
||||
or
|
||||
nonLocationHeaderSanitizer(node)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,13 @@ class RedirectAppendCall extends MethodAccess {
|
||||
}
|
||||
|
||||
/** A URL redirection sink from spring controller method. */
|
||||
class SpringUrlRedirectSink extends DataFlow::Node {
|
||||
SpringUrlRedirectSink() {
|
||||
abstract class SpringUrlRedirectSink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sink for URL Redirection via the Spring View classes.
|
||||
*/
|
||||
private class SpringViewUrlRedirectSink extends SpringUrlRedirectSink {
|
||||
SpringViewUrlRedirectSink() {
|
||||
exists(RedirectBuilderExpr rbe |
|
||||
rbe.getRightOperand() = this.asExpr() and
|
||||
any(SpringRequestMappingMethod sqmm).polyCalls*(this.getEnclosingCallable())
|
||||
@@ -71,3 +76,64 @@ class SpringUrlRedirectSink extends DataFlow::Node {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for URL Redirection via the `ResponseEntity` class.
|
||||
*/
|
||||
private class SpringResponseEntityUrlRedirectSink extends SpringUrlRedirectSink {
|
||||
SpringResponseEntityUrlRedirectSink() {
|
||||
// Find `new ResponseEntity(httpHeaders, ...)` or
|
||||
// `new ResponseEntity(..., httpHeaders, ...)` sinks
|
||||
exists(ClassInstanceExpr cie, Argument argument |
|
||||
cie.getConstructedType() instanceof SpringResponseEntity and
|
||||
argument.getType() instanceof SpringHttpHeaders and
|
||||
argument = cie.getArgument([0, 1]) and
|
||||
this.asExpr() = argument
|
||||
)
|
||||
or
|
||||
// Find `ResponseEntity.status(...).headers(taintHeaders).build()` or
|
||||
// `ResponseEntity.status(...).location(URI.create(taintURL)).build()` sinks
|
||||
exists(MethodAccess ma |
|
||||
ma.getMethod()
|
||||
.getDeclaringType()
|
||||
.hasQualifiedName("org.springframework.http", "ResponseEntity$HeadersBuilder<BodyBuilder>") and
|
||||
ma.getMethod().getName() in ["headers", "location"] and
|
||||
this.asExpr() = ma.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class HttpHeadersMethodAccess extends MethodAccess {
|
||||
HttpHeadersMethodAccess() { this.getMethod().getDeclaringType() instanceof SpringHttpHeaders }
|
||||
}
|
||||
|
||||
private class HttpHeadersAddSetMethodAccess extends HttpHeadersMethodAccess {
|
||||
HttpHeadersAddSetMethodAccess() { this.getMethod().getName() in ["add", "set"] }
|
||||
}
|
||||
|
||||
private class HttpHeadersSetLocationMethodAccess extends HttpHeadersMethodAccess {
|
||||
HttpHeadersSetLocationMethodAccess() { this.getMethod().hasName("setLocation") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fromNode` to `toNode` is a dataflow step from a tainted argument to
|
||||
* a `HttpHeaders` instance qualifier, i.e. `httpHeaders.setLocation(tainted)`.
|
||||
*/
|
||||
predicate springUrlRedirectTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
exists(HttpHeadersSetLocationMethodAccess ma |
|
||||
fromNode.asExpr() = ma.getArgument(0) and
|
||||
toNode.asExpr() = ma.getQualifier()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer to exclude the cases where the `HttpHeaders.add` or `HttpHeaders.set`
|
||||
* methods are called with a HTTP header other than "Location".
|
||||
* E.g: `httpHeaders.add("X-Some-Header", taintedUrlString)`
|
||||
*/
|
||||
predicate nonLocationHeaderSanitizer(DataFlow::Node node) {
|
||||
exists(HttpHeadersAddSetMethodAccess ma, Argument firstArg | node.asExpr() = ma.getArgument(1) |
|
||||
firstArg = ma.getArgument(0) and
|
||||
not firstArg.(CompileTimeConstantExpr).getStringValue().matches("Location")
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user