During remote class loading attacks, the presence and configuration of a SecurityManager is a crucial point that can limit
the impact of a vulnerable server. Without a SecurityManager present, Java RMI is not allowed to access codebases that
were not explicilty allowed during startup (server side codebases). Attempting a codebase attack on a server that does not
use a SecuriyManager results in the famous no security manager: RMI class loader disabled
error message.
When writing RMI applications from scratch, adding a SecurityManager is fully optional. This is probably the most common
usage scenario and servers that do not use a SecurityManager are encountered quite often. However, when using pre-existing
RMI components, they may inlcude a SecurityManager by default. One example for this situation is the default rmiregistry
utility that is shipped with Java installations. This utility starts an RMI registry service and sets a SecurityManager
by default.
Even with a SecurityManager present, codebase attacks can still be restricted by the AccessControlContexts
that are
defined for the targeted component. Whereas the SecurityManager configuration on the application level fully depends on a
user defined policy, internal RMI communication uses separate and preconfigured AccessControlContexts
for the different
RMI components.
In this document, some of the default AccessControlContexts
used by internal RMI communication are explained in more
detail. The code samples in this documet were copied from the current state (at the time of writing) of the openjdk GitHub
repository.
-
The
LoaderHandler.java
class is responsible for remote class loading and its SecurityManager settings are therefore probably the most important. -
Within it's
loadClass
method, theLoaderHandler
stops class loading on its own and delegates the request to the parent class loader when no SecurityManager is defined:SecurityManager sm = System.getSecurityManager(); if (sm == null) { try { Class<?> c = Class.forName(name, false, parent); if (loaderLog.isLoggable(Log.VERBOSE)) { loaderLog.log(Log.VERBOSE, "class \"" + name + "\" found via " + "thread context class loader " + "(no security manager: codebase disabled), " + "defined by " + c.getClassLoader()); } return c; } catch (ClassNotFoundException e) { if (loaderLog.isLoggable(Log.BRIEF)) { loaderLog.log(Log.BRIEF, "class \"" + name + "\" not found via " + "thread context class loader " + "(no security manager: codebase disabled)", e); } throw new ClassNotFoundException(e.getMessage() + " (no security manager: RMI class loader disabled)", e.getException()); } }
-
When a SecurityManager is defined, the actual loader is created within an
AccessControlContext
that includes permissions only for the specified codebase. That means:- File system permissions for the corresponding folder on the file system, if the codebase is a file system URL.
- Network permissions for the corresponding remote host, if the codebase is a remote URL.
-
The
AccessControlContext
is created by thegetLoaderAccessControlContext
function:private static AccessControlContext getLoaderAccessControlContext( URL[] urls) { [...] // createClassLoader permission needed to create loader in context perms.add(new RuntimePermission("createClassLoader")); // add permissions to read any "java.*" property perms.add(new java.util.PropertyPermission("java.*","read")); // add permissions reuiqred to load from codebase URL path addPermissionsForURLs(urls, perms, true); [...] }
-
Apart from it's own
AccessControlContext
, the loader also always checks if the calling code has the required security permissions. This check is performed within thecheckPermissions
function.private void checkPermissions() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { // should never be null? Enumeration<Permission> enum_ = permissions.elements(); while (enum_.hasMoreElements()) { sm.checkPermission(enum_.nextElement()); } } }
-
Summarized: Without a manually defined security policy, classes loaded in remote class loading attacks are always restricted by the
LoaderHandler
. Effectively, they are only allowed to access their own codebase, to read java properties and to create new class loaders with the same security permissions. Furthermore, the parentAccessControlContext
(e.g. the one of the RMI registry or the DGC) must also allow access to the requested operation.
-
The RMI registry defines it's
AccessControlContext
in theRegistryImpl
class, within thegetAccessControlContext
function. -
Apart from the default permissions, custom permissions can be added by specifying a custom policy file.
-
Relevant permission definitions are:
perms.add(new SocketPermission("*", "connect,accept")); perms.add(new SocketPermission("localhost:"+port, "listen,accept")); perms.add(new RuntimePermission("accessClassInPackage.sun.jvmstat.*")); perms.add(new RuntimePermission("accessClassInPackage.sun.jvm.hotspot.*")); perms.add(new FilePermission("<<ALL FILES>>", "read"));
-
With these permissions, the RMI registry allows remote class loading in general. However, the
<<ALL FILES>>
permission does not effectively apply, as it is overwritten by theAccessControlContext
used by theLoaderHandler
. -
The RMI registry may uses a SecurityManager by default. Whether this is the case depends on the mechanism the RMI registry is initialized. When using the
createRegistry
function from theRegistryImpl
class to create the registry, a SecurityManager is always used:public static RegistryImpl createRegistry(int regPort) throws RemoteException { // Create and install the security manager if one is not installed already. if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); } [...]
This is the function used by the
rmiregistry
utility that shipped with Java installations. However, when using thecreateRegistry
function from theLocateRegistry
to create the registry, the corresponding object is created directly without using a SecurityManager:public static Registry createRegistry(int port) throws RemoteException { return new RegistryImpl(port); }
-
The
AccessControlContext
for inbound DGC traffic is defined inDGCImpl.java
within a static block. -
Custom policy files are ignored by the DGC and only the hard coded permissions are used.
-
The hard coded permissions are rather strict:
Permissions perms = new Permissions(); perms.add(new SocketPermission("*", "accept,resolve")); ProtectionDomain[] pd = { new ProtectionDomain(null, perms) }; AccessControlContext acceptAcc = new AccessControlContext(pd);
-
This means, that the DGC does always reject remote class loading attempts, as it's security policy does not even allow outbound connections.
-
Additionally (although not really related to the SecurityManager),
UnicastServerRef
checks whether a call is targeting the DGC during the dispatching process. If the target is the DGC,useCodebaseOnly
is explicitly set totrue
for theMarshalInputStream
processing the call.Class<?> clazz = Class.forName("sun.rmi.transport.DGCImpl_Skel"); if (clazz.isAssignableFrom(skel.getClass())) { ((MarshalInputStream)in).useCodebaseOnly(); }
-
Therefore, remote class loading should never work on a modern DGC instance.
-
Outbound DGC connections are usually not used for remote class loading attacks, but theoretically it is possible. To perform such an attack, one could use the
JRMPClient
ysoserial gadget to create an outbound DGC call to a custom listener. This listener needs to return an annotated, non existing class as return value. WhenuseCodebaseOnly
is set tofalse
this should trigger remote class loading as usual. -
The
AccessControlContext
for outbound DGC connections is defined in theDGCClient
class within a static property namedSOCKET_ACC
:private static final AccessControlContext SOCKET_ACC; static { Permissions perms = new Permissions(); perms.add(new SocketPermission("*", "connect,resolve")); ProtectionDomain[] pd = { new ProtectionDomain(null, perms) }; SOCKET_ACC = new AccessControlContext(pd); }
-
The corresponding
AccessControlContext
is used for all calls to thedirty
andclean
methods and allows outbound connections only. -
As the
LoaderHandler
class requestsconnect,accept
socket permissions within it'saddPermissionsForURLs
function, remote class loading would probably be rejected by theLoaderHandler
.