Added query for insecure environment configuration RMI JMX (CVE-2016-8735)

This commit is contained in:
Timo Mueller
2021-04-30 16:23:17 +02:00
parent ecd40e5cae
commit 15a3068f8a
7 changed files with 375 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.rmi.registry.LocateRegistry;
import java.util.HashMap;
import java.util.Map;
import javax.management.MBeanServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
public class CorrectJmxInitialisation {
public void initAndStartJmxServer() throws IOException{
int jmxPort = 1919;
LocateRegistry.createRegistry(jmxPort);
/* Restrict the login function to String Objects only (see CVE-2016-3427) */
Map<String, Object> env = new HashMap<String, Object>();
// For Java 10+
String my_filter = "java.lang.String;!*"; // Deny everything but java.lang.String
env.put(RMIConnectorServer.CREDENTIALS_FILTER_PATTERN, my_filter);
/* Old way
env.put("jmx.remote.rmi.server.credential.types",
new String[] { String[].class.getName(), String.class.getName() });
*/
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
JMXServiceURL jmxUrl = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + jmxPort + "/jmxrmi");
// Create JMXConnectorServer in a secure manner
javax.management.remote.JMXConnectorServer connectorServer = JMXConnectorServerFactory
.newJMXConnectorServer(jmxUrl, env, beanServer);
connectorServer.start();
}
}

View File

@@ -0,0 +1,34 @@
public class CorrectRmiInitialisation {
public void initAndStartRmiServer(int port, String hostname, boolean local) {
Map<String, Object> env = new HashMap<>();
MBeanServerForwarder authzProxy = null;
env.put("jmx.remote.x.daemon", "true");
/* Restrict the login function to String Objects only (see CVE-2016-3427) */
Map<String, Object> env = new HashMap<String, Object>();
// For Java 10+
String my_filter = "java.lang.String;!*"; // Deny everything but java.lang.String
env.put(RMIConnectorServer.CREDENTIALS_FILTER_PATTERN, my_filter);
/* Old way
env.put("jmx.remote.rmi.server.credential.types",
new String[] { String[].class.getName(), String.class.getName() });
*/
int rmiPort = Integer.getInteger("com.sun.management.jmxremote.rmi.port", 0);
RMIJRMPServerImpl server = new RMIJRMPServerImpl(rmiPort,
(RMIClientSocketFactory) env.get(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE),
(RMIServerSocketFactory) env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE), env);
JMXServiceURL serviceURL = new JMXServiceURL("rmi", hostname, rmiPort);
// Create RMI Server
RMIConnectorServer jmxServer = new RMIConnectorServer(serviceURL, env, server,
ManagementFactory.getPlatformMBeanServer());
jmxServer.start();
}
}

View File

@@ -0,0 +1,68 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>An improperly set environment variable during the creation of an RMI or JMX server can lead
to an unauthenticated remote code execution vulnerability. This is due to the fact that the
RMI/JMX server environment allows attackers to supply arbitrary objects to the authentication
method, resulting in the attempted deserialization of an attacker-controlled object.
</overview>
<recommendation>
<p>During the creation/initialitation of an RMI or JMX server a properly set environment (Map) variable has
to be passed as second parameter.
In order to disallow the deserialization of arbitrary objects the passed environment needs to set a deserialization filter.
Ideally this filter only allows the deserialization to <code>java.lang.String</code>.
The filter can be configured by setting the key <code>jmx.remote.rmi.server.credentials.filter.pattern</code> (CONST variable <code>RMIConnectorServer.CREDENTIALS_FILTER_PATTERN</code>).
The filter should (ideally) blacklist all classes, and only whitelist java.lang.String for deserialization: (<code> "java.lang.String;!*"</code>).
The key-value pair can be set as following:
<code>
String my_filter = "java.lang.String;!*"; // Deny everything but java.lang.String
Map<String, Object> env = new HashMap<String, Object>;
env.put(RMIConnectorServer.CREDENTIALS_FILTER_PATTERN, my_filter);
</code>
For applications using &lt; Java 10:
<code>
// This is deprecated in Java 10+ !
Map<String, Object> env = new HashMap<String, Object>;
env.put (
"jmx.remote.rmi.server.credential.types",
new String[]{
String[].class.getName(),
String.class.getName()
}
);
</code>
Please note that the authentication implementation is vulnerable by default.
For this reason an initialitation with a <code>null</code> environment is also vulnerable .
</recommendation>
<example>
<p>The following examples show how an RMI or JMX server can be initialized securely.
<p>The first example shows how an RMI server can be initialized with a secure environment.</p>
<sample src="CorrectRmiInitialisation.java">
<p>The second example shows how the environment for a JMX server can be initialized securely.</p>
<sample src="CorrectJmxInitialisation.java">
</example>
<references>
<li>OWASP: <a href="https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data">OWASP Deserialization of untrusted data</a>.</li>
<li>Issue discovered in Tomcat (CVE-2016-8735): <a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-8735">OWASP ESAPI</a>.</li>
<li>Vulnerable implementation of the RMI "newClient()" function: <a href="https://docs.oracle.com/javase/8/docs/api/javax/management/remote/rmi/RMIServer.html#newClient-java.lang.Object-">Vulnerable Function</a>.</li>
<li>Oracle release notes fixing the issue: <a href="https://www.oracle.com/java/technologies/javase/8u91-relnotes.html">Rlease Notes</a>.</li>
<li>Documentation for <a href="https://docs.oracle.com/javase/10/docs/api/javax/management/remote/rmi/RMIConnectorServer.html#CREDENTIALS_FILTER_PATTERN">CREDENTIALS_FILTER_PATTERN</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,145 @@
/**
* @name InsecureRmiJmxAuthenticationEnvironment
* @kind path-problem
* @problem.severity error
* @tags security
* external/cwe/cwe-665
* @precision high
* @id java/insecure-rmi-jmx-server-initalisation
*/
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.DataFlow2
import semmle.code.java.Maps
import DataFlow::PathGraph
import semmle.code.java.dataflow.NullGuards
import semmle.code.java.dataflow.Nullness
/** predicate which detects vulnerable Constructors */
predicate isRmiOrJmxServerCreateConstructor(Constructor constructor) {
constructor.getName() = "RMIConnectorServer" and
constructor
.getDeclaringType()
.hasQualifiedName("javax.management.remote.rmi", "RMIConnectorServer")
}
/** Predicate which detects vulnerable server creations via methods */
predicate isRmiOrJmxServerCreateMethod(Method method) {
method.getName() = "newJMXConnectorServer" and
method.getDeclaringType().hasQualifiedName("javax.management.remote", "JMXConnectorServerFactory")
}
/**
* Models flow from `new HashMap<>()` to a
* `map.put("jmx.remote.rmi.server.credential.types", value)` call.
*/
class MapToPutCredentialstypeConfiguration extends DataFlow2::Configuration {
MapToPutCredentialstypeConfiguration() { this = "MapToPutCredentialstypeConfiguration" }
override predicate isSource(DataFlow::Node source) {
source.asExpr().(ClassInstanceExpr).getConstructedType() instanceof MapType
}
override predicate isSink(DataFlow2::Node sink) { putsCredentialtypesKey(sink.asExpr()) }
/**
* Holds if a `put` call on `qualifier` puts a key match
* into the map.
*/
private predicate putsCredentialtypesKey(Expr qualifier) {
exists(MapPutCall put |
put.getKey().(CompileTimeConstantExpr).getStringValue() =
"jmx.remote.rmi.server.credential.types" or
put.getKey().(CompileTimeConstantExpr).getStringValue() =
"jmx.remote.rmi.server.credentials.filter.pattern" or
put.getKey().toString() = "RMIConnectorServer.CREDENTIAL_TYPES" or // This can probably be solved more nicely
put.getKey().toString() = "RMIConnectorServer.CREDENTIALS_FILTER_PATTERN" // This can probably be solved more nicely
|
put.getQualifier() = qualifier and
put.getMethod().(MapMethod).getReceiverKeyType().getName() = "String" and
put.getMethod().(MapMethod).getReceiverValueType().getName() = "Object"
)
}
}
/** Models flow from `new HashMap<>()` to the argument of a `TestConstructor` call. */
class MapToRmiServerInitConfiguration extends DataFlow::Configuration {
MapToRmiServerInitConfiguration() { this = "MapToRmiServerInitConfiguration" }
override predicate isSource(DataFlow::Node source) {
source.asExpr().(ClassInstanceExpr).getConstructedType() instanceof MapType
}
override predicate isSink(DataFlow::Node sink) {
exists(ConstructorCall ccall |
sink.asExpr() = ccall.getArgument(1) and
isRmiOrJmxServerCreateConstructor(ccall.getConstructor())
)
or
exists(MethodAccess ma |
sink.asExpr() = ma.getArgument(1) and
isRmiOrJmxServerCreateMethod(ma.getMethod())
)
}
}
/** Models if any JMX/RMI server are initialized with a null environment */
class FlowServerInitializedWithNullEnv extends DataFlow::Configuration {
FlowServerInitializedWithNullEnv() { this = "FlowServerInitializedWithNullEnv" }
override predicate isSource(DataFlow::Node source) { any() }
override predicate isSink(DataFlow::Node sink) {
exists(ConstructorCall ccall |
sink.asExpr() = ccall and
isRmiOrJmxServerCreateConstructor(ccall.getConstructor()) and
ccall.getArgument(1) = alwaysNullExpr()
)
or
exists(MethodAccess ma |
sink.asExpr() = ma and
isRmiOrJmxServerCreateMethod(ma.getMethod()) and
ma.getArgument(1) = alwaysNullExpr()
)
}
}
/** Returns true if within the passed PathNode a "jmx.remote.rmi.server.credential.types" is set. */
predicate mapFlowContainsCredentialtype(DataFlow::PathNode source) {
exists(MapToPutCredentialstypeConfiguration conf | conf.hasFlow(source.getNode(), _))
}
/** Returns result depending if the vulnerability is present due to a) a null environment b) an insecurely set environment map */
bindingset[source]
string getRmiResult(DataFlow::PathNode source) {
// We got a Map so we have a source and a sink node
if source.getNode().getType() instanceof MapType
then
result =
"RMI/JMX server initialized with insecure environment $@. The $@ never restricts accepted client objects to 'java.lang.String'. This exposes to deserialization attacks against the RMI authentication method."
else
// The environment is not a map so we most likely have a "null" environment and therefore only a sink
result =
"RMI/JMX server initialized with 'null' environment $@. Missing type restriction in RMI authentication method exposes the application to deserialization attacks."
}
/** Predicate returns true for any map flow paths with NO jmx.remote.rmi.server.credential.types set */
predicate hasVulnerableMapFlow(DataFlow::PathNode source, DataFlow::PathNode sink) {
exists(MapToRmiServerInitConfiguration dataflow |
dataflow.hasFlowPath(source, sink) and
not mapFlowContainsCredentialtype(source)
)
}
from
DataFlow::PathNode source, DataFlow::PathNode sink,
FlowServerInitializedWithNullEnv initNullDataflow
where
// Check if server is created with null env
initNullDataflow.hasFlowPath(source, sink)
or
// The map created by `new HashMap<String, Object>()` has to a) flow to the sink and b) there must not exist a (different) sink that would put `"jmx.remote.rmi.server.credential.types"` into `source`. */
hasVulnerableMapFlow(source, sink)
select sink.getNode(), source, sink, getRmiResult(source), sink.getNode(), "here", source.getNode(),
"source environment 'Map'"

View File

@@ -0,0 +1,18 @@
edges
| InsecureRmiServerInitialisation.java:32:31:32:45 | new HashMap<String,Object>(...) : HashMap | InsecureRmiServerInitialisation.java:34:59:34:61 | env |
| InsecureRmiServerInitialisation.java:39:31:39:45 | new HashMap<String,Object>(...) : HashMap | InsecureRmiServerInitialisation.java:43:59:43:61 | env |
| InsecureRmiServerInitialisation.java:57:31:57:45 | new HashMap<String,Object>(...) : HashMap | InsecureRmiServerInitialisation.java:61:59:61:61 | env |
nodes
| InsecureRmiServerInitialisation.java:13:5:13:69 | newJMXConnectorServer(...) | semmle.label | newJMXConnectorServer(...) |
| InsecureRmiServerInitialisation.java:32:31:32:45 | new HashMap<String,Object>(...) : HashMap | semmle.label | new HashMap<String,Object>(...) : HashMap |
| InsecureRmiServerInitialisation.java:34:59:34:61 | env | semmle.label | env |
| InsecureRmiServerInitialisation.java:39:31:39:45 | new HashMap<String,Object>(...) : HashMap | semmle.label | new HashMap<String,Object>(...) : HashMap |
| InsecureRmiServerInitialisation.java:43:59:43:61 | env | semmle.label | env |
| InsecureRmiServerInitialisation.java:57:31:57:45 | new HashMap<String,Object>(...) : HashMap | semmle.label | new HashMap<String,Object>(...) : HashMap |
| InsecureRmiServerInitialisation.java:61:59:61:61 | env | semmle.label | env |
#select
| InsecureRmiServerInitialisation.java:13:5:13:69 | newJMXConnectorServer(...) | InsecureRmiServerInitialisation.java:13:5:13:69 | newJMXConnectorServer(...) | InsecureRmiServerInitialisation.java:13:5:13:69 | newJMXConnectorServer(...) | RMI/JMX server initialized with 'null' environment $@. Missing type restriction in RMI authentication method exposes the application to deserialization attacks. | InsecureRmiServerInitialisation.java:13:5:13:69 | newJMXConnectorServer(...) | here | InsecureRmiServerInitialisation.java:13:5:13:69 | newJMXConnectorServer(...) | source environment 'Map' |
| InsecureRmiServerInitialisation.java:34:59:34:61 | env | InsecureRmiServerInitialisation.java:32:31:32:45 | new HashMap<String,Object>(...) : HashMap | InsecureRmiServerInitialisation.java:34:59:34:61 | env | RMI/JMX server initialized with insecure environment $@. The $@ never restricts accepted client objects to 'java.lang.String'. This exposes to deserialization attacks against the RMI authentication method. | InsecureRmiServerInitialisation.java:34:59:34:61 | env | here | InsecureRmiServerInitialisation.java:32:31:32:45 | new HashMap<String,Object>(...) | source environment 'Map' |
| InsecureRmiServerInitialisation.java:61:59:61:61 | env | InsecureRmiServerInitialisation.java:57:31:57:45 | new HashMap<String,Object>(...) : HashMap | InsecureRmiServerInitialisation.java:61:59:61:61 | env | RMI/JMX server initialized with insecure environment $@. The $@ never restricts accepted client objects to 'java.lang.String'. This exposes to deserialization attacks against the RMI authentication method. | InsecureRmiServerInitialisation.java:61:59:61:61 | env | here | InsecureRmiServerInitialisation.java:57:31:57:45 | new HashMap<String,Object>(...) | source environment 'Map' |
TODO RMI Server is missing due to import errors (See test java file)

View File

@@ -0,0 +1,71 @@
import java.io.IOException;
import javax.management.remote.JMXConnectorServerFactory;
// import javax.management.remote.rmi.RMIConnectorServer; Importing this throws an error, therefore we can't test this
import java.util.HashMap;
import java.util.Map;
public class InsecureRmiServerInitialisation {
public void initInsecureJmxDueToNullEnv() throws IOException {
// Bad initializing env (arg1) with null
JMXConnectorServerFactory.newJMXConnectorServer(null, null, null);
}
public void initInsecureRmiDueToNullEnv() throws IOException {
// Bad initializing env (arg1) with null
// new RMIConnectorServer(null, null, null, null); Importing this throws an error, therefore we can't test this
}
public void initInsecureRmiDueToMissingEnvKeyValue() throws IOException {
// Bad initializing env (arg1) with missing
// "jmx.remote.rmi.server.credential.types"
Map<String, Object> env = new HashMap<>();
env.put("jmx.remote.x.daemon", "true");
// new RMIConnectorServer(null, env, null, null); Importing this throws an error, therefore we can't test this
}
public void initInsecureJmxDueToMissingEnvKeyValue() throws IOException {
// Bad initializing env (arg1) with missing
// "jmx.remote.rmi.server.credential.types"
Map<String, Object> env = new HashMap<>();
env.put("jmx.remote.x.daemon", "true");
JMXConnectorServerFactory.newJMXConnectorServer(null, env, null);
}
public void secureJmxConnnectorServer() throws IOException {
// Good
Map<String, Object> env = new HashMap<>();
env.put("jmx.remote.x.daemon", "true");
env.put("jmx.remote.rmi.server.credential.types",
new String[] { String[].class.getName(), String.class.getName() });
JMXConnectorServerFactory.newJMXConnectorServer(null, env, null);
}
public void secureRmiConnnectorServer() throws IOException {
// Good
Map<String, Object> env = new HashMap<>();
env.put("jmx.remote.x.daemon", "true");
env.put("jmx.remote.rmi.server.credential.types",
new String[] { String[].class.getName(), String.class.getName() });
// new RMIConnectorServer(null, env, null, null); Importing this throws an error, therefore we can't test this
}
public void secureeJmxConnectorServerConstants() throws IOException {
// Good
Map<String, Object> env = new HashMap<>();
env.put("jmx.remote.x.daemon", "true");
env.put("RMIConnectorServer.SERIAL_FILTER_PATTERN",
new String[] { String[].class.getName(), String.class.getName() });
JMXConnectorServerFactory.newJMXConnectorServer(null, env, null);
}
public void secureeRmiConnectorServerConstants() throws IOException {
// Good
Map<String, Object> env = new HashMap<>();
env.put("jmx.remote.x.daemon", "true");
env.put("RMIConnectorServer.SERIAL_FILTER_PATTERN",
new String[] { String[].class.getName(), String.class.getName() });
// new RMIConnectorServer(null, env, null, null); Importing this throws an error, therefore we can't test this
}
}

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-665/InsecureRmiServerInitialisation.ql