Securing the out-of-the-box management agent with an SSL/TLS-based RMI Registry

The aim of this post is to comment and shed some light on the security warning message that appears in the J2SE 5.0 Monitoring and Management Guide concerning the use of an insecure RMI registry by the out-of-the-box management agent.

WARNING: A potential security issue has been identified with password authentication for remote connectors when the client obtains the remote connector from an insecure RMI registry (the default). If an attacker starts a bogus RMI registry on the target server before the legitimate registry is started, then the attacker can steal clients' passwords. This scenario includes the case where you launch a Java VM with remote management enabled, using the system property, even when SSL is enabled. Although such attacks are likely to be noticed, it is nevertheless a vulnerability.

To avoid this problem, use SSL client certificates for authentication instead of passwords, or ensure that the client obtains the remote connector object securely, for example through a secure LDAP server or a file in a shared secure filesystem.


One of the known limitations of the out-of-the-box managament agent in J2SE 5.0 is that there is no way to start it in a fully secure mode "as is" due to the lack of the required management properties to specify that we want to run the agent with an SSL/TLS-based RMI registry in the JRE_HOME/lib/management/ configuration file mainly due to some limitations in the JNDI/RMI Registry Service Provider itself.

Also from a client's standpoint we cannot tell the connector client when it's created through the JMXConnectorFactory to abort the connection to the RMI registry when trying to retrieve the RMIServer stub if it's not SSL/TLS-protected thus preventing the connector client to send any credentials to a rogue connector server.

How have these issues been solved in JDK 6?

On the server side we've defined a new property in the file,

When this property is set to true, an RMI registry protected by SSL will be created and configured by the out-of-the-box management agent when the JVM is started. If this property is set to true, in order to have full security then SSL client authentication must also be enabled through the already existing property in the file,

Then in the client side, Sun's JDK 6 implementation has introduced a new environment property to be passed in to the connector client's environment map at connection time which allows to tell the JNDI/RMI Registry Service Provider to use the supplied RMIClientSocketFactory when interacting with the RMI registry.

JMXServiceURL url = ...;
Map<String,Object> env = new HashMap<String,Object>();
env.put("com.sun.jndi.rmi.factory.socket", new SslRMIClientSocketFactory());
JMXConnector cc = JMXConnectorFactory.connect(url, env);
MBeanServerConnection mbsc = cc.getMBeanServerConnection();

How could we achieve the equivalent behavior in J2SE 5.0?

On the server side, we will have to mimick the out-of-the-box managament agent but this time make it use an SSL/TLS-protected RMI registry, create the RMIServer remote object, instantiate the RMI connector server with this new RMIServer remote object, start it and finally bind the exported RMIServer remote object to the RMI registry.
package com.example;

import java.rmi.registry.\*;
import java.util.\*;
import javax.rmi.ssl.\*;

public class MyApp {
    public static void main(String[] args) throws Exception {
        // Ensure cryptographically strong random number generator used
        // to choose the object number - see java.rmi.server.ObjID
        System.setProperty("java.rmi.server.randomIDs", "true");
        // Start a secure RMI registry on port 3000.
        System.out.println("Create a secure RMI registry on port 3000");
        SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory();
        SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory(null, null, true);
        Registry registry = LocateRegistry.createRegistry(3000, csf, ssf);
        // Retrieve the PlatformMBeanServer.
        System.out.println("Get the platform's MBean server");
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        // Environment map.
        System.out.println("Initialize the environment map");
        Map<String,Object> env = new HashMap<String,Object>();
        // Provide the password file used by the connector server to
        // perform user authentication. The password file is a properties
        // based text file specifying username/password pairs.
        env.put("jmx.remote.x.password.file", "");
        // Provide the access level file used by the connector server to
        // perform user authorization. The access level file is a properties
        // based text file specifying username/access level pairs where
        // access level is either "readonly" or "readwrite" access to the
        // MBeanServer operations.
        env.put("jmx.remote.x.access.file", "");
        // Create and start an RMI connector server.
        // As specified in the JMXServiceURL the RMIServer stub will be
        // registered in the RMI registry running in the local host on
        // port 3000 with the name "jmxrmi". This is the same name the
        // out-of-the-box management agent uses to register the RMIServer
        // stub too.
        // JMXServiceURL = "service:jmx:rmi:///jndi/rmi://:3000/jmxrmi"
        System.out.println("Create and start an RMI connector server");
        JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://");
        RMIJRMPServerImpl server = new RMIJRMPServerImpl(0, csf, ssf, env);
        RMIConnectorServer cs = new RMIConnectorServer(url, env, server, mbs);
        registry.bind("jmxrmi", server);
        System.out.println("Waiting for incoming connections...");

On the client side, we will have to look up the RMIServer stub from the SSL/TLS-based RMI registry, create an RMIConnector with the retrieved stub and then connect it to the connector server.
package com.example;

import java.rmi.registry.\*;
import java.util.\*;
import javax.rmi.ssl.SslRMIClientSocketFactory;

public class MyClient {
    public static void main(String[] args) throws Exception {
        // Environment map
        System.out.println("\\nInitialize the environment map");
        Map<String,Object> env = new HashMap<String,Object>();
        // Provide the credentials required by the server to successfully
        // perform user authentication
        String[] credentials = new String[] { "username" , "password" };
        env.put("jmx.remote.credentials", credentials);
        // Create an RMI connector client and
        // connect it to the RMI connector server
        System.out.println("\\nCreate an RMI connector client and " +
                "connect it to the RMI connector server");
        SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory();
        Registry registry = LocateRegistry.getRegistry(null, 3000, csf);
        RMIServer stub = (RMIServer) registry.lookup("jmxrmi");
        RMIConnector jmxc = new RMIConnector(stub, env);
        // Get an MBeanServerConnection
        System.out.println("\\nGet an MBeanServerConnection");
        MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
        // Get domains from MBeanServer
        String domains[] = mbsc.getDomains();
        for (int i = 0; i < domains.length; i++) {
            System.out.println("\\tDomain[" + i + "] = " + domains[i]);
        // Get MBean count
        System.out.println("\\nMBean count = " + mbsc.getMBeanCount());
        // Close MBeanServer connection
        System.out.println("\\nClose the connection to the server");
        System.out.println("\\nBye! Bye!");

In order to run the application open a shell window and call:

  • $ java com.example.MyApp

In order to run the client open another shell window in the same machine and call:

  • $ java com.example.MyClient

Mr. Alventosa, Thank you for the follow-up post! Due to your efforts here, I'm optimistic about securing JMX and including it as a feature of our applications. I'll give it a try... Take care, Steve

Posted by Steve T on November 18, 2006 at 02:59 PM CET #

You might find the commons-ssl java library interesting for taking more control over those SSL sockets (expiry checking, crl checking, hostname checking, etc...). It also allows one to avoid using the "" system property.

There's also some support for doing RMI over SSL, but right now the RMI client will automatically downgrade to plain-socket if it detects the server doesn't support SSL. Not so secure. It's really just an experiment at this time and needs some more thought.

Requires Java 1.4 (I used Throwable.getCause() to detect that server is plain-socket).

Posted by Julius Davies on November 23, 2006 at 09:26 AM CET #

Post a Comment:
  • HTML Syntax: NOT allowed



« January 2017