mirror of
https://github.com/github/codeql.git
synced 2026-05-01 19:55:15 +02:00
Merge pull request #5435 from haby0/DynamicallyLoadedClasses
Java: CWE-470 Use of Externally-Controlled Input to Select Classes or Code ('Unsafe Reflection')
This commit is contained in:
@@ -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()
|
||||
)
|
||||
}
|
||||
@@ -47,6 +47,9 @@ private XMLElement elementReferencingType(RefType rt) {
|
||||
}
|
||||
|
||||
abstract private class ReflectiveClassIdentifier extends Expr {
|
||||
/**
|
||||
* Gets the type of a class identified by this expression.
|
||||
*/
|
||||
abstract RefType getReflectivelyIdentifiedClass();
|
||||
}
|
||||
|
||||
@@ -59,7 +62,7 @@ private class ReflectiveClassIdentifierLiteral extends ReflectiveClassIdentifier
|
||||
/**
|
||||
* 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(..)`.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
edges
|
||||
| UnsafeReflection.java:21:28:21:60 | getParameter(...) : String | UnsafeReflection.java:25:29:25:59 | getDeclaredConstructors(...) : Constructor[] |
|
||||
| UnsafeReflection.java:21:28:21:60 | getParameter(...) : String | UnsafeReflection.java:25:29:25:62 | ...[...] |
|
||||
| UnsafeReflection.java:22:33:22:70 | getParameter(...) : String | UnsafeReflection.java:25:76:25:89 | parameterValue |
|
||||
| UnsafeReflection.java:25:29:25:59 | getDeclaredConstructors(...) : Constructor[] | UnsafeReflection.java:25:29:25:62 | ...[...] |
|
||||
| UnsafeReflection.java:33:28:33:60 | getParameter(...) : String | UnsafeReflection.java:39:13:39:38 | getDeclaredMethods(...) : Method[] |
|
||||
| UnsafeReflection.java:33:28:33:60 | getParameter(...) : String | UnsafeReflection.java:39:13:39:41 | ...[...] |
|
||||
| UnsafeReflection.java:33:28:33:60 | getParameter(...) : String | UnsafeReflection.java:39:50:39:55 | object |
|
||||
| UnsafeReflection.java:34:33:34:70 | getParameter(...) : String | UnsafeReflection.java:39:58:39:71 | parameterValue |
|
||||
| UnsafeReflection.java:39:13:39:38 | getDeclaredMethods(...) : Method[] | UnsafeReflection.java:39:13:39:41 | ...[...] |
|
||||
| UnsafeReflection.java:46:24:46:82 | beanIdOrClassName : String | UnsafeReflection.java:53:30:53:46 | beanIdOrClassName : String |
|
||||
| UnsafeReflection.java:46:132:46:168 | body : Map | UnsafeReflection.java:49:37:49:40 | body : Map |
|
||||
| UnsafeReflection.java:49:23:49:59 | (...)... : Object | UnsafeReflection.java:53:67:53:73 | rawData : Object |
|
||||
| UnsafeReflection.java:49:37:49:40 | body : Map | UnsafeReflection.java:49:37:49:59 | get(...) : Object |
|
||||
| UnsafeReflection.java:49:37:49:59 | get(...) : Object | UnsafeReflection.java:49:23:49:59 | (...)... : Object |
|
||||
| UnsafeReflection.java:53:30:53:46 | beanIdOrClassName : String | UnsafeReflection.java:104:34:104:57 | beanIdOrClassName : String |
|
||||
| UnsafeReflection.java:53:67:53:73 | rawData : Object | UnsafeReflection.java:104:102:104:118 | data : Object |
|
||||
| UnsafeReflection.java:62:33:62:70 | getParameter(...) : String | UnsafeReflection.java:68:76:68:89 | parameterValue |
|
||||
| UnsafeReflection.java:77:33:77:70 | getParameter(...) : String | UnsafeReflection.java:83:76:83:89 | parameterValue |
|
||||
| UnsafeReflection.java:92:33:92:70 | getParameter(...) : String | UnsafeReflection.java:98:76:98:89 | parameterValue |
|
||||
| UnsafeReflection.java:104:34:104:57 | beanIdOrClassName : String | UnsafeReflection.java:119:21:119:26 | method |
|
||||
| UnsafeReflection.java:104:34:104:57 | beanIdOrClassName : String | UnsafeReflection.java:119:35:119:38 | bean |
|
||||
| UnsafeReflection.java:104:102:104:118 | data : Object | UnsafeReflection.java:119:41:119:44 | data |
|
||||
nodes
|
||||
| UnsafeReflection.java:21:28:21:60 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| UnsafeReflection.java:22:33:22:70 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| UnsafeReflection.java:25:29:25:59 | getDeclaredConstructors(...) : Constructor[] | semmle.label | getDeclaredConstructors(...) : Constructor[] |
|
||||
| UnsafeReflection.java:25:29:25:62 | ...[...] | semmle.label | ...[...] |
|
||||
| UnsafeReflection.java:25:76:25:89 | parameterValue | semmle.label | parameterValue |
|
||||
| UnsafeReflection.java:33:28:33:60 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| UnsafeReflection.java:34:33:34:70 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| UnsafeReflection.java:39:13:39:38 | getDeclaredMethods(...) : Method[] | semmle.label | getDeclaredMethods(...) : Method[] |
|
||||
| UnsafeReflection.java:39:13:39:41 | ...[...] | semmle.label | ...[...] |
|
||||
| UnsafeReflection.java:39:50:39:55 | object | semmle.label | object |
|
||||
| UnsafeReflection.java:39:58:39:71 | parameterValue | semmle.label | parameterValue |
|
||||
| UnsafeReflection.java:46:24:46:82 | beanIdOrClassName : String | semmle.label | beanIdOrClassName : String |
|
||||
| UnsafeReflection.java:46:132:46:168 | body : Map | semmle.label | body : Map |
|
||||
| UnsafeReflection.java:49:23:49:59 | (...)... : Object | semmle.label | (...)... : Object |
|
||||
| UnsafeReflection.java:49:37:49:40 | body : Map | semmle.label | body : Map |
|
||||
| UnsafeReflection.java:49:37:49:59 | get(...) : Object | semmle.label | get(...) : Object |
|
||||
| UnsafeReflection.java:53:30:53:46 | beanIdOrClassName : String | semmle.label | beanIdOrClassName : String |
|
||||
| UnsafeReflection.java:53:67:53:73 | rawData : Object | semmle.label | rawData : Object |
|
||||
| UnsafeReflection.java:62:33:62:70 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| UnsafeReflection.java:68:76:68:89 | parameterValue | semmle.label | parameterValue |
|
||||
| UnsafeReflection.java:77:33:77:70 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| UnsafeReflection.java:83:76:83:89 | parameterValue | semmle.label | parameterValue |
|
||||
| UnsafeReflection.java:92:33:92:70 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| UnsafeReflection.java:98:76:98:89 | parameterValue | semmle.label | parameterValue |
|
||||
| UnsafeReflection.java:104:34:104:57 | beanIdOrClassName : String | semmle.label | beanIdOrClassName : String |
|
||||
| UnsafeReflection.java:104:102:104:118 | data : Object | semmle.label | data : Object |
|
||||
| UnsafeReflection.java:119:21:119:26 | method | semmle.label | method |
|
||||
| UnsafeReflection.java:119:35:119:38 | bean | semmle.label | bean |
|
||||
| UnsafeReflection.java:119:41:119:44 | data | semmle.label | data |
|
||||
#select
|
||||
| UnsafeReflection.java:25:29:25:62 | ...[...] | UnsafeReflection.java:21:28:21:60 | getParameter(...) : String | UnsafeReflection.java:25:29:25:62 | ...[...] | Unsafe reflection of $@. | UnsafeReflection.java:21:28:21:60 | getParameter(...) | user input |
|
||||
| UnsafeReflection.java:39:13:39:41 | ...[...] | UnsafeReflection.java:33:28:33:60 | getParameter(...) : String | UnsafeReflection.java:39:13:39:41 | ...[...] | Unsafe reflection of $@. | UnsafeReflection.java:33:28:33:60 | getParameter(...) | user input |
|
||||
| UnsafeReflection.java:119:21:119:26 | method | UnsafeReflection.java:46:24:46:82 | beanIdOrClassName : String | UnsafeReflection.java:119:21:119:26 | method | Unsafe reflection of $@. | UnsafeReflection.java:46:24:46:82 | beanIdOrClassName | user input |
|
||||
@@ -0,0 +1,144 @@
|
||||
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 {
|
||||
|
||||
@GetMapping(value = "uf1")
|
||||
public void bad1(HttpServletRequest request) {
|
||||
String className = request.getParameter("className");
|
||||
String parameterValue = request.getParameter("parameterValue");
|
||||
try {
|
||||
Class clazz = Class.forName(className);
|
||||
Object object = clazz.getDeclaredConstructors()[0].newInstance(parameterValue); //bad
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping(value = "uf2")
|
||||
public void bad2(HttpServletRequest request) {
|
||||
String className = request.getParameter("className");
|
||||
String parameterValue = request.getParameter("parameterValue");
|
||||
try {
|
||||
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
|
||||
Class clazz = classLoader.loadClass(className);
|
||||
Object object = clazz.newInstance();
|
||||
clazz.getDeclaredMethods()[0].invoke(object, parameterValue); //bad
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(value = {"/service/{beanIdOrClassName}/{methodName}"}, method = {RequestMethod.POST}, consumes = {"application/json"}, produces = {"application/json"})
|
||||
public Object bad3(@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 = "uf3")
|
||||
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 = "uf4")
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping(value = "uf5")
|
||||
public void good3(HttpServletRequest request) throws Exception {
|
||||
String className = request.getParameter("className");
|
||||
String parameterValue = request.getParameter("parameterValue");
|
||||
if (!className.equals("com.example.test1")){ //good
|
||||
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 @@
|
||||
experimental/Security/CWE/CWE-470/UnsafeReflection.ql
|
||||
@@ -0,0 +1 @@
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/springframework-5.3.8/
|
||||
@@ -6,4 +6,4 @@ import java.lang.annotation.*;
|
||||
@Retention(value=RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Indexed
|
||||
public @interface Component { }
|
||||
public @interface Component { }
|
||||
@@ -11,4 +11,5 @@ import java.lang.annotation.Target;
|
||||
@Documented
|
||||
public @interface PathVariable {
|
||||
|
||||
String value() default "";
|
||||
}
|
||||
Reference in New Issue
Block a user