mirror of
https://github.com/github/codeql.git
synced 2026-03-06 07:36:47 +01:00
Merge branch 'main' into atorralba/promote-mvel-injection
This commit is contained in:
@@ -1,63 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Java EXpression Language (JEXL) is a simple expression language
|
||||
provided by the Apache Commons JEXL library.
|
||||
The syntax is close to a mix of ECMAScript and shell-script.
|
||||
The language allows invocation of methods available in the JVM.
|
||||
If a JEXL expression is built using attacker-controlled data,
|
||||
and then evaluated, then it may allow the attacker to run arbitrary code.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
It is generally recommended to avoid using untrusted input in a JEXL expression.
|
||||
If it is not possible, JEXL expressions should be run in a sandbox that allows accessing only
|
||||
explicitly allowed classes.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example uses untrusted data to build and run a JEXL expression.
|
||||
</p>
|
||||
<sample src="UnsafeJexlExpressionEvaluation.java" />
|
||||
|
||||
<p>
|
||||
The next example shows how an untrusted JEXL expression can be run
|
||||
in a sandbox that allows accessing only methods in the <code>java.lang.Math</code> class.
|
||||
The sandbox is implemented using <code>JexlSandbox</code> class that is provided by
|
||||
Apache Commons JEXL 3.
|
||||
</p>
|
||||
<sample src="SaferJexlExpressionEvaluationWithSandbox.java" />
|
||||
|
||||
<p>
|
||||
The next example shows another way how a sandbox can be implemented.
|
||||
It uses a custom implementation of <code>JexlUberspect</code>
|
||||
that checks if callees are instances of allowed classes.
|
||||
</p>
|
||||
<sample src="SaferJexlExpressionEvaluationWithUberspectSandbox.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Apache Commons JEXL:
|
||||
<a href="https://commons.apache.org/proper/commons-jexl/">Project page</a>.
|
||||
</li>
|
||||
<li>
|
||||
Apache Commons JEXL documentation:
|
||||
<a href="https://commons.apache.org/proper/commons-jexl/javadocs/apidocs-2.1.1/">JEXL 2.1.1 API</a>.
|
||||
</li>
|
||||
<li>
|
||||
Apache Commons JEXL documentation:
|
||||
<a href="https://commons.apache.org/proper/commons-jexl/apidocs/index.html">JEXL 3.1 API</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 (JEXL)
|
||||
* @description Evaluation of a user-controlled JEXL expression
|
||||
* may lead to arbitrary code execution.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/jexl-expression-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import java
|
||||
import JexlInjectionLib
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, JexlInjectionConfig conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "JEXL injection from $@.", source.getNode(), "this user input"
|
||||
@@ -1,277 +0,0 @@
|
||||
import java
|
||||
import FlowUtils
|
||||
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 JEXL expression.
|
||||
* It supports both JEXL 2 and 3.
|
||||
*/
|
||||
class JexlInjectionConfig extends TaintTracking::Configuration {
|
||||
JexlInjectionConfig() { this = "JexlInjectionConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof JexlEvaluationSink }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
any(TaintPropagatingJexlMethodCall c).taintFlow(fromNode, toNode) or
|
||||
hasGetterFlow(fromNode, toNode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for Expresssion Language injection vulnerabilities via Jexl,
|
||||
* i.e. method calls that run evaluation of a JEXL expression.
|
||||
*
|
||||
* Creating a `Callable` from a tainted JEXL expression or script is considered as a sink
|
||||
* although the tainted expression is not executed at this point.
|
||||
* Here we assume that it will get executed at some point,
|
||||
* maybe stored in an object field and then reached by a different flow.
|
||||
*/
|
||||
private class JexlEvaluationSink extends DataFlow::ExprNode {
|
||||
JexlEvaluationSink() {
|
||||
exists(MethodAccess ma, Method m, Expr taintFrom |
|
||||
ma.getMethod() = m and taintFrom = this.asExpr()
|
||||
|
|
||||
m instanceof DirectJexlEvaluationMethod and ma.getQualifier() = taintFrom
|
||||
or
|
||||
m instanceof CreateJexlCallableMethod and ma.getQualifier() = taintFrom
|
||||
or
|
||||
m instanceof JexlEngineGetSetPropertyMethod and
|
||||
taintFrom.getType() instanceof TypeString and
|
||||
ma.getAnArgument() = taintFrom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines method calls that propagate tainted data via one of the methods
|
||||
* from JEXL library.
|
||||
*/
|
||||
private class TaintPropagatingJexlMethodCall extends MethodAccess {
|
||||
Expr taintFromExpr;
|
||||
|
||||
TaintPropagatingJexlMethodCall() {
|
||||
exists(Method m, RefType taintType |
|
||||
this.getMethod() = m and
|
||||
taintType = taintFromExpr.getType()
|
||||
|
|
||||
isUnsafeEngine(this.getQualifier()) and
|
||||
(
|
||||
m instanceof CreateJexlScriptMethod and
|
||||
taintFromExpr = this.getArgument(0) and
|
||||
taintType instanceof TypeString
|
||||
or
|
||||
m instanceof CreateJexlExpressionMethod and
|
||||
taintFromExpr = this.getAnArgument() and
|
||||
taintType instanceof TypeString
|
||||
or
|
||||
m instanceof CreateJexlTemplateMethod and
|
||||
(taintType instanceof TypeString or taintType instanceof Reader) and
|
||||
taintFromExpr = this.getArgument([0, 1])
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fromNode` to `toNode` is a dataflow step that propagates
|
||||
* tainted data.
|
||||
*/
|
||||
predicate taintFlow(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
fromNode.asExpr() = taintFromExpr and toNode.asExpr() = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `expr` is a JEXL engine that is not configured with a sandbox.
|
||||
*/
|
||||
private predicate isUnsafeEngine(Expr expr) {
|
||||
not exists(SandboxedJexlFlowConfig config | config.hasFlowTo(DataFlow::exprNode(expr)))
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration for a tracking sandboxed JEXL engines.
|
||||
*/
|
||||
private class SandboxedJexlFlowConfig extends DataFlow2::Configuration {
|
||||
SandboxedJexlFlowConfig() { this = "JexlInjection::SandboxedJexlFlowConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node instanceof SandboxedJexlSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) {
|
||||
exists(MethodAccess ma, Method m | ma.getMethod() = m |
|
||||
(
|
||||
m instanceof CreateJexlScriptMethod or
|
||||
m instanceof CreateJexlExpressionMethod or
|
||||
m instanceof CreateJexlTemplateMethod
|
||||
) and
|
||||
ma.getQualifier() = node.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
createsJexlEngine(fromNode, toNode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a data flow source for JEXL engines configured with a sandbox.
|
||||
*/
|
||||
private class SandboxedJexlSource extends DataFlow::ExprNode {
|
||||
SandboxedJexlSource() {
|
||||
exists(MethodAccess ma, Method m | m = ma.getMethod() |
|
||||
m.getDeclaringType() instanceof JexlBuilder and
|
||||
m.hasName(["uberspect", "sandbox"]) and
|
||||
m.getReturnType() instanceof JexlBuilder and
|
||||
this.asExpr() = [ma, ma.getQualifier()]
|
||||
)
|
||||
or
|
||||
exists(ConstructorCall cc |
|
||||
cc.getConstructedType() instanceof JexlEngine and
|
||||
cc.getArgument(0).getType() instanceof JexlUberspect and
|
||||
cc = this.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fromNode` to `toNode` is a dataflow step that creates one of the JEXL engines.
|
||||
*/
|
||||
private predicate createsJexlEngine(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
exists(MethodAccess ma, Method m | m = ma.getMethod() |
|
||||
(m.getDeclaringType() instanceof JexlBuilder or m.getDeclaringType() instanceof JexlEngine) and
|
||||
m.hasName(["create", "createJxltEngine"]) and
|
||||
ma.getQualifier() = fromNode.asExpr() and
|
||||
ma = toNode.asExpr()
|
||||
)
|
||||
or
|
||||
exists(ConstructorCall cc |
|
||||
cc.getConstructedType() instanceof UnifiedJexl and
|
||||
cc.getArgument(0) = fromNode.asExpr() and
|
||||
cc = toNode.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A methods in the `JexlEngine` class that gets or sets a property with a JEXL expression.
|
||||
*/
|
||||
private class JexlEngineGetSetPropertyMethod extends Method {
|
||||
JexlEngineGetSetPropertyMethod() {
|
||||
getDeclaringType() instanceof JexlEngine and
|
||||
hasName(["getProperty", "setProperty"])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that triggers direct evaluation of JEXL expressions.
|
||||
*/
|
||||
private class DirectJexlEvaluationMethod extends Method {
|
||||
DirectJexlEvaluationMethod() {
|
||||
getDeclaringType() instanceof JexlExpression and hasName("evaluate")
|
||||
or
|
||||
getDeclaringType() instanceof JexlScript and hasName("execute")
|
||||
or
|
||||
getDeclaringType() instanceof JxltEngineExpression and hasName(["evaluate", "prepare"])
|
||||
or
|
||||
getDeclaringType() instanceof JxltEngineTemplate and hasName("evaluate")
|
||||
or
|
||||
getDeclaringType() instanceof UnifiedJexlExpression and hasName(["evaluate", "prepare"])
|
||||
or
|
||||
getDeclaringType() instanceof UnifiedJexlTemplate and hasName("evaluate")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that creates a JEXL script.
|
||||
*/
|
||||
private class CreateJexlScriptMethod extends Method {
|
||||
CreateJexlScriptMethod() { getDeclaringType() instanceof JexlEngine and hasName("createScript") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that creates a `Callable` for a JEXL expression or script.
|
||||
*/
|
||||
private class CreateJexlCallableMethod extends Method {
|
||||
CreateJexlCallableMethod() {
|
||||
(getDeclaringType() instanceof JexlExpression or getDeclaringType() instanceof JexlScript) and
|
||||
hasName("callable")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that creates a JEXL template.
|
||||
*/
|
||||
private class CreateJexlTemplateMethod extends Method {
|
||||
CreateJexlTemplateMethod() {
|
||||
(getDeclaringType() instanceof JxltEngine or getDeclaringType() instanceof UnifiedJexl) and
|
||||
hasName("createTemplate")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that creates a JEXL expression.
|
||||
*/
|
||||
private class CreateJexlExpressionMethod extends Method {
|
||||
CreateJexlExpressionMethod() {
|
||||
(getDeclaringType() instanceof JexlEngine or getDeclaringType() instanceof JxltEngine) and
|
||||
hasName("createExpression")
|
||||
or
|
||||
getDeclaringType() instanceof UnifiedJexl and hasName("parse")
|
||||
}
|
||||
}
|
||||
|
||||
private class JexlRefType extends RefType {
|
||||
JexlRefType() { getPackage().hasName(["org.apache.commons.jexl2", "org.apache.commons.jexl3"]) }
|
||||
}
|
||||
|
||||
private class JexlExpression extends JexlRefType {
|
||||
JexlExpression() { hasName(["Expression", "JexlExpression"]) }
|
||||
}
|
||||
|
||||
private class JexlScript extends JexlRefType {
|
||||
JexlScript() { hasName(["Script", "JexlScript"]) }
|
||||
}
|
||||
|
||||
private class JexlBuilder extends JexlRefType {
|
||||
JexlBuilder() { hasName("JexlBuilder") }
|
||||
}
|
||||
|
||||
private class JexlEngine extends JexlRefType {
|
||||
JexlEngine() { hasName("JexlEngine") }
|
||||
}
|
||||
|
||||
private class JxltEngine extends JexlRefType {
|
||||
JxltEngine() { hasName("JxltEngine") }
|
||||
}
|
||||
|
||||
private class UnifiedJexl extends JexlRefType {
|
||||
UnifiedJexl() { hasName("UnifiedJEXL") }
|
||||
}
|
||||
|
||||
private class JexlUberspect extends Interface {
|
||||
JexlUberspect() {
|
||||
hasQualifiedName("org.apache.commons.jexl2.introspection", "Uberspect") or
|
||||
hasQualifiedName("org.apache.commons.jexl3.introspection", "JexlUberspect")
|
||||
}
|
||||
}
|
||||
|
||||
private class JxltEngineExpression extends NestedType {
|
||||
JxltEngineExpression() { getEnclosingType() instanceof JxltEngine and hasName("Expression") }
|
||||
}
|
||||
|
||||
private class JxltEngineTemplate extends NestedType {
|
||||
JxltEngineTemplate() { getEnclosingType() instanceof JxltEngine and hasName("Template") }
|
||||
}
|
||||
|
||||
private class UnifiedJexlExpression extends NestedType {
|
||||
UnifiedJexlExpression() { getEnclosingType() instanceof UnifiedJexl and hasName("Expression") }
|
||||
}
|
||||
|
||||
private class UnifiedJexlTemplate extends NestedType {
|
||||
UnifiedJexlTemplate() { getEnclosingType() instanceof UnifiedJexl and hasName("Template") }
|
||||
}
|
||||
|
||||
private class Reader extends RefType {
|
||||
Reader() { hasQualifiedName("java.io", "Reader") }
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
* @description Evaluation of a user-controlled malicious expression in Java Python
|
||||
* interpreter may lead to remote code execution.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/jython-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
public void evaluate(Socket socket) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(socket.getInputStream()))) {
|
||||
|
||||
JexlSandbox onlyMath = new JexlSandbox(false);
|
||||
onlyMath.white("java.lang.Math");
|
||||
JexlEngine jexl = new JexlBuilder().sandbox(onlyMath).create();
|
||||
|
||||
String input = reader.readLine();
|
||||
JexlExpression expression = jexl.createExpression(input);
|
||||
JexlContext context = new MapContext();
|
||||
expression.evaluate(context);
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
public void evaluate(Socket socket) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(socket.getInputStream()))) {
|
||||
|
||||
JexlUberspect sandbox = new JexlUberspectSandbox();
|
||||
JexlEngine jexl = new JexlBuilder().uberspect(sandbox).create();
|
||||
|
||||
String input = reader.readLine();
|
||||
JexlExpression expression = jexl.createExpression(input);
|
||||
JexlContext context = new MapContext();
|
||||
expression.evaluate(context);
|
||||
}
|
||||
|
||||
private static class JexlUberspectSandbox implements JexlUberspect {
|
||||
|
||||
private static final List<String> ALLOWED_CLASSES =
|
||||
Arrays.asList("java.lang.Math", "java.util.Random");
|
||||
|
||||
private final JexlUberspect uberspect = new JexlBuilder().create().getUberspect();
|
||||
|
||||
private void checkAccess(Object obj) {
|
||||
if (!ALLOWED_CLASSES.contains(obj.getClass().getCanonicalName())) {
|
||||
throw new AccessControlException("Not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlMethod getMethod(Object obj, String method, Object... args) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getMethod(obj, method, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PropertyResolver> getResolvers(JexlOperator op, Object obj) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getResolvers(op, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClassLoader(ClassLoader loader) {
|
||||
uberspect.setClassLoader(loader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return uberspect.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlMethod getConstructor(Object obj, Object... args) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getConstructor(obj, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlPropertyGet getPropertyGet(Object obj, Object identifier) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getPropertyGet(obj, identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlPropertyGet getPropertyGet(List<PropertyResolver> resolvers, Object obj, Object identifier) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getPropertyGet(resolvers, obj, identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlPropertySet getPropertySet(Object obj, Object identifier, Object arg) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getPropertySet(obj, identifier, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlPropertySet getPropertySet(List<PropertyResolver> resolvers, Object obj, Object identifier, Object arg) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getPropertySet(resolvers, obj, identifier, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<?> getIterator(Object obj) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getIterator(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic) {
|
||||
return uberspect.getArithmetic(arithmetic);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
public void evaluate(Socket socket) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(socket.getInputStream()))) {
|
||||
|
||||
String input = reader.readLine();
|
||||
JexlEngine jexl = new JexlBuilder().create();
|
||||
JexlExpression expression = jexl.createExpression(input);
|
||||
JexlContext context = new MapContext();
|
||||
expression.evaluate(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
public void bindRemoteObject(Registry registry, int port) throws Exception {
|
||||
ObjectInputFilter filter = info -> {
|
||||
if (info.serialClass().getCanonicalName().startsWith("com.safe.package.")) {
|
||||
return ObjectInputFilter.Status.ALLOWED;
|
||||
}
|
||||
return ObjectInputFilter.Status.REJECTED;
|
||||
};
|
||||
registry.bind("safer", UnicastRemoteObject.exportObject(new RemoteObjectImpl(), port, filter));
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
public class Server {
|
||||
public void bindRemoteObject(Registry registry) throws Exception {
|
||||
registry.bind("safe", new RemoteObjectImpl());
|
||||
}
|
||||
}
|
||||
|
||||
interface RemoteObject extends Remote {
|
||||
void calculate(int a, double b) throws RemoteException;
|
||||
void save(String s) throws RemoteException;
|
||||
}
|
||||
|
||||
class RemoteObjectImpl implements RemoteObject {
|
||||
// ...
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
public class Server {
|
||||
public void bindRemoteObject(Registry registry) throws Exception {
|
||||
registry.bind("unsafe", new RemoteObjectImpl());
|
||||
}
|
||||
}
|
||||
|
||||
interface RemoteObject extends Remote {
|
||||
void action(Object obj) throws RemoteException;
|
||||
}
|
||||
|
||||
class RemoteObjectImpl implements RemoteObject {
|
||||
// ...
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Java RMI uses the default Java serialization mechanism (in other words, <code>ObjectInputStream</code>)
|
||||
to pass parameters in remote method invocations. This mechanism is known to be unsafe when deserializing
|
||||
untrusted data. If a registered remote object has a method that accepts a complex object,
|
||||
an attacker can take advantage of the unsafe deserialization mechanism.
|
||||
In the worst case, it results in remote code execution.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Use only strings and primitive types for parameters of remotely invokable methods.
|
||||
</p>
|
||||
<p>
|
||||
Set a filter for incoming serialized data by wrapping remote objects using either <code>UnicastRemoteObject.exportObject(Remote, int, ObjectInputFilter)</code>
|
||||
or <code>UnicastRemoteObject.exportObject(Remote, int, RMIClientSocketFactory, RMIServerSocketFactory, ObjectInputFilter)</code> methods.
|
||||
Those methods accept an <code>ObjectInputFilter</code> that decides which classes are allowed for deserialization.
|
||||
The filter should allow deserializing only safe classes.
|
||||
</p>
|
||||
<p>
|
||||
It is also possible to set a process-wide deserialization filter.
|
||||
The filter can be set by with <code>ObjectInputFilter.Config.setSerialFilter(ObjectInputFilter)</code> method,
|
||||
or by setting system or security property <code>jdk.serialFilter</code>.
|
||||
Make sure that you use the latest Java versions that include JEP 290.
|
||||
Please note that the query is not sensitive to this mitigation.
|
||||
</p>
|
||||
<p>
|
||||
If switching to the latest Java versions is not possible,
|
||||
consider using other implementations of remote procedure calls. For example, HTTP API with JSON.
|
||||
Make sure that the underlying deserialization mechanism is properly configured
|
||||
so that deserialization attacks are not possible.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following code registers a remote object
|
||||
with a vulnerable method that accepts a complex object:
|
||||
</p>
|
||||
<sample src="RmiUnsafeRemoteObject.java" />
|
||||
|
||||
<p>
|
||||
The next example registers a safe remote object
|
||||
whose methods use only primitive types and strings:
|
||||
</p>
|
||||
<sample src="RmiSafeRemoteObject.java" />
|
||||
|
||||
<p>
|
||||
The next example shows how to set a deserilization filter for a remote object:
|
||||
</p>
|
||||
<sample src="RmiRemoteObjectWithFilter.java" />
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Oracle:
|
||||
<a href="https://www.oracle.com/java/technologies/javase/remote-method-invocation-home.html">Remote Method Invocation (RMI)</a>.
|
||||
</li>
|
||||
<li>
|
||||
ITNEXT:
|
||||
<a href="https://itnext.io/java-rmi-for-pentesters-part-two-reconnaissance-attack-against-non-jmx-registries-187a6561314d">Java RMI for pentesters part two - reconnaissance & attack against non-JMX registries</a>.
|
||||
</li>
|
||||
<li>
|
||||
MOGWAI LABS:
|
||||
<a href="https://mogwailabs.de/en/blog/2019/03/attacking-java-rmi-services-after-jep-290">Attacking Java RMI services after JEP 290</a>
|
||||
</li>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/Deserialization_of_untrusted_data">Deserialization of untrusted data</a>.
|
||||
</li>
|
||||
<li>
|
||||
OpenJDK:
|
||||
<a href="https://openjdk.java.net/jeps/290">JEP 290: Filter Incoming Serialization Data</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @name Unsafe deserialization in a remotely callable method.
|
||||
* @description If a registered remote object has a method that accepts a complex object,
|
||||
* an attacker can take advantage of the unsafe deserialization mechanism
|
||||
* which is used to pass parameters in RMI.
|
||||
* In the worst case, it results in remote code execution.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/unsafe-deserialization-rmi
|
||||
* @tags security
|
||||
* external/cwe/cwe-502
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.frameworks.Rmi
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A method that binds a name to a remote object.
|
||||
*/
|
||||
private class BindMethod extends Method {
|
||||
BindMethod() {
|
||||
(
|
||||
getDeclaringType().hasQualifiedName("java.rmi", "Naming") or
|
||||
getDeclaringType().hasQualifiedName("java.rmi.registry", "Registry")
|
||||
) and
|
||||
hasName(["bind", "rebind"])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `type` has an vulnerable remote method.
|
||||
*/
|
||||
private predicate hasVulnerableMethod(RefType type) {
|
||||
exists(RemoteCallableMethod m, Type parameterType |
|
||||
m.getDeclaringType() = type and parameterType = m.getAParamType()
|
||||
|
|
||||
not parameterType instanceof PrimitiveType and
|
||||
not parameterType instanceof TypeString and
|
||||
not parameterType.(RefType).hasQualifiedName("java.io", "ObjectInputStream")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for unsafe remote objects
|
||||
* that are vulnerable to deserialization attacks.
|
||||
*/
|
||||
private class BindingUnsafeRemoteObjectConfig extends TaintTracking::Configuration {
|
||||
BindingUnsafeRemoteObjectConfig() { this = "BindingUnsafeRemoteObjectConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
exists(ConstructorCall cc | cc = source.asExpr() |
|
||||
hasVulnerableMethod(cc.getConstructedType().getASupertype*())
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(MethodAccess ma | ma.getArgument(1) = sink.asExpr() |
|
||||
ma.getMethod() instanceof BindMethod
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
exists(MethodAccess ma, Method m | m = ma.getMethod() |
|
||||
m.getDeclaringType().hasQualifiedName("java.rmi.server", "UnicastRemoteObject") and
|
||||
m.hasName("exportObject") and
|
||||
not m.getParameterType([2, 4]).(RefType).hasQualifiedName("java.io", "ObjectInputFilter") and
|
||||
ma.getArgument(0) = fromNode.asExpr() and
|
||||
ma = toNode.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, BindingUnsafeRemoteObjectConfig conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Unsafe deserialization in a remote object."
|
||||
Reference in New Issue
Block a user