mirror of
https://github.com/github/codeql.git
synced 2025-12-21 03:06:31 +01:00
Added query for insecure environment configuration RMI JMX (CVE-2016-8735)
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 < 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>
|
||||
@@ -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'"
|
||||
Reference in New Issue
Block a user