|
|
||
Eamonn McManus's BlogSecuring the RMI registryPosted by emcmanus on December 29, 2006 at 09:39 AM | Comments (4)If you've had occasion to use the RMI registry seriously, you may have encountered some of its shortcomings. Chief of these is that anybody on the local machine can modify the registry. There are only a few things you can do about that, of which the craziest is to reimplement enough of RMI to code your own compatible version of the registry. I did that, and write about it in a later entry, but then I discovered a simpler alternative, due to Peter Jones. We encountered this problem when defining the JMX Remote API.
The standard connector defined by this API is the RMI connector,
and the obvious way for it to work would have been to interpret a
URL like Well, if you have a look at the documentation, you'll see that things are quite a bit more complicated than that. Addresses come in two forms, of which the first is this rather ugly one: service:jmx:rmi:///jndi/rmi://somehost:8888/bar That is as nothing to the ugliness of the second form, though: service:jmx:rmi://somehost/stub/rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LnJlbW90ZS5ybWk uUk1JU2VydmVySW1wbF9TdHViAAAAAAAAAAICAAB4cgAaamF2YS5ybWkuc2VydmVyLlJlbW90ZVN0dW Lp/tzJi+FlGgIAAHhyABxqYXZhLnJtaS5zZXJ2ZXIuUmVtb3RlT2JqZWN002G0kQxhMx4DAAB4cHc5A AtVbmljYXN0UmVmMgAADjEyOS4xNTcuMjAzLjM5AACauPDsdTz1J8m+BsXsHAAAAQ/Ow/uqgAEAeA== Blechh. As Spec Lead for JSR 160, I bear considerable responsibility for this ugliness, and you can believe that I didn't accept it gladly. Why we perpetrated these address formsThere were two problems that led us to exclude the simple approach with the RMI registry. The first was that a longstanding bug in the JDK meant that you couldn't have more than one RMI registry in the same VM. So there was great potential for interference between different modules each needing to create a registry. This bug was fixed in JDK 1.5 but JSR 160 was supported all the way back to 1.3. The second problem was the one I mentioned above, namely that the only access check on changes to the RMI registry is that the client making the change must be connecting from the same machine that the registry is running on. This means that if you store your JMX Connector Server in an RMI registry on a multiuser machine, some other unscrupulous user can come along and replace it with their own RMI object. Users would then connect to the rogue object without realising it, and send their password or other credentials to authenticate. Securing the RMI registryWe've addressed this problem in a couple of ways. First of all, you can protect your registry using SSL client authentication. Luis-Miguel Alventosa has explained this in detail in his blog. The big advantage of using SSL is that clients can be sure they are connecting to the server they are expecting, and not a server being run by Dr Evil on the same port. But that's server authentication. Using SSL client authentication seems redundant given that the client can authenticate to the JMX Connector Server with a password or other credentials. You only really need the SSL part to protect the registry from rogue modifications. And setting up the necessary SSL client certificates is a non-trivial amount of work. The second possibility is to access implementation-specific
We can get away with this because this code is part of the same
JDK as the implementation-dependent classes it is accessing. I
strongly discourage anyone from doing this sort of thing outside
the JDK. The A portable (if hacky) solutionA viable alternative is described in the Work Around section of RFE 5029435. The access check to see if the caller is on the same machine can be faked so that it always fails, even if the caller is on the same machine. The idea is that the caller's address comes from Socket.getInetAddress(). By imaginative use of RMI socket factories and Socket subclasses, we can arrange for this method never to return a local address. The access check is only
made when the registry is accessed via RMI. It is not made if
the
Registry r = LocateRegistry.createRegistry(
8888, null, new NoLocalAddressServerSocketFactory());
Foo fooObject = new Foo();
Remote fooStub = UnicastRemoteObject.exportObject(fooObject, 0);
r.bind("foo", fooStub);
Bar barObject = new Bar();
Remote barStub = UnicastRemoteObject.exportObject(barObject, 0);
r.bind("bar", barObject);
The
public class NoLocalAddressServerSocketFactory
implements RMIServerSocketFactory {
public ServerSocket createServerSocket(int port) throws IOException {
return new NoLocalAddressServerSocket(port);
}
private static class NoLocalAddressServerSocket extends ServerSocket {
NoLocalAddressServerSocket(int port) throws IOException {
super(port);
}
@Override
public Socket accept() throws IOException {
Socket s = new NoLocalAddressSocket();
super.implAccept(s);
return s;
}
}
private static class NoLocalAddressSocket extends Socket {
@Override
public InetAddress getInetAddress() {
return null;
}
}
}
It's a bit hacky, but it does the trick! You're not limited to rejecting all modifications. For example, suppose you wanted to allow all modifications, whether or not the caller is remote, and security be damned. Then you could simply return InetAddress.getLocalHost() instead of null. Or, if you've exported the registry through SSL (see below), then you might allow modifications only from clients that have been authenticated through SSL, but you would allow lookups from all clients. So you would return InetAddress.getLocalHost() if the client is authenticated and null otherwise. If you are already using a socket factoryThings are a bit more complicated if you are already using an RMIServerSocketFactory, such as SslRMIServerSocketFactory. In that case, you'll need to combine subclassing with delegation, as outlined here:
public class NoLocalAddressServerSocketFactory2
implements RMIServerSocketFactory {
private final RMIServerSocketFactory factory;
public NoLocalAddressServerSocketFactory2(RMIServerSocketFactory f) {
if (f == null) {
f = RMISocketFactory.getSocketFactory();
if (f == null)
f = RMISocketFactory.getDefaultSocketFactory();
}
this.factory = f;
}
public ServerSocket createServerSocket(int port) throws IOException {
ServerSocket ss = factory.createServerSocket(port);
return new NoLocalAddressServerSocket(ss);
}
private static class NoLocalAddressServerSocket extends ServerSocket {
private final ServerSocket serverSocket;
NoLocalAddressServerSocket(ServerSocket ss) throws IOException {
this.serverSocket = ss;
}
@Override
public Socket accept() throws IOException {
return new NoLocalAddressSocket(serverSocket.accept());
}
@Override
public void close() throws IOException {
serverSocket.close();
}
...override 13 other ServerSocket methods in the same way as close()...
}
private static class NoLocalAddressSocket extends Socket {
private final Socket socket;
NoLocalAddressSocket(Socket s) {
this.socket = s;
}
@Override
public InetAddress getInetAddress() {
return null;
}
@Override
public void close() throws IOException {
socket.close();
}
...override 38 more Socket methods in the same way as close()...
}
}
The main drawback that I can see is that if a future version of
Credit where it's dueThe public bug database is anonymized but I can reveal that this idea, like so many RMI ideas, is the work of the discreet genius Peter Jones. Bookmark blog post: CommentsComments are listed in date ascending order (oldest first) | Post Comment
| ||
|
|