Understanding Java RMI and JRMP Security: Deserialization Risks and RMG Exploitation

Understanding RMI and JRMP Architecture

Remote Method Invocation (RMI) enables Java applications to invoke methods on objects residing in separate JVMs across a network. It abstracts the underlying communication layer, allowing developers to interact with remote references as if they were local instances. The data exchange relies on the Java Remote Method Protocol (JRMP), a proprietary transport protocol built atop TCP/IP. JRMP structures the serialized byte streams that carry method calls, parameters, and return values. Because RMI heavily depends on Java's native serialization mechanism, improper input validation can expose critical deserialization vulnerabilities.

Identifying Deserialization Entry Points

Security assessments of RMI deployments typically target two primary deserialization triggers on the server side. While client-side exploitation is theoretically possible, server-side attack vectors are far more prevalent in real-world scenarios.

  • RegistryImpl_Skel.dispatch: Manages registry operations including binding, listing, and looking up remote objects. When a client triggers bind() or lookup(), the registry deserializes incoming parameters or returns serialized stubs. Injecting malicious objects into these calls can cause immediate gadget chain execution during the deserialization phase.
  • UnicastRef.unmarshalValue: Handles the deserialization of return values after a remote method invocation. Clients serialize arguments via marshalValue() and transmit them to the server. Upon processing, the server returns a result. If the response is tampered with or if the client blindly deserializes untrusted return data, arbitrary code execution becomes feasible.

Lab Environment Setup

A minimal RMI client-server pair demonstrates the communication flow and potential attack surface.

Server Implementation

package com.demo.remoting;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface TaskExecutor extends Remote {
    String runOperation(String payload) throws RemoteException;
}
package com.demo.remoting.impl;

import com.demo.remoting.TaskExecutor;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class TaskHandler extends UnicastRemoteObject implements TaskExecutor {
    protected TaskHandler() throws RemoteException {
        super();
    }

    @Override
    public String runOperation(String input) throws RemoteException {
        return "Processed: " + input;
    }
}
package com.demo.server;

import com.demo.remoting.TaskExecutor;
import com.demo.remoting.impl.TaskHandler;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class ServerLauncher {
    public static void main(String[] args) {
        try {
            TaskExecutor service = new TaskHandler();
            Registry rmiRegistry = LocateRegistry.createRegistry(1099);
            rmiRegistry.bind("TaskService", service);
            System.out.println("RMI Registry active on port 1099");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Client Implementation

package com.demo.client;

import com.demo.remoting.TaskExecutor;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class ClientLauncher {
    public static void main(String[] args) {
        try {
            Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
            TaskExecutor remoteStub = (TaskExecutor) registry.lookup("TaskService");
            System.out.println(remoteStub.runOperation("TestPayload"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Exploitation Mechanics and Deserialization Triggers

Standard RMI communications safely serialize primitive and string types. To trigger a vulnerability, an attacker substitutes expected parameters with crafted gadget chains. For example, replacing a String agrument with a malicious HashMap containing URLDNS references forces the JVM to perform DNS resolution during object graph deserialization.

When the client invokes the modified method, the payload traverses the JRMP stream. The server's registry dispatch or remote object handler deserializes the object graph. If the payload aligns with the expected method signature or bypasses type validation through hash manipulation, the embedded exploit executes.

Protocol Structure and Traffic Analysis

Network captures reveal that RMI operates over two distinct ports: the fixed registry port (commonly 1099) and a dynamically assigned remote object port. Initial handshakes exchange JRMP versioning data. Subsequent packets contain structured serialization headers:

  • AC ED 00 05: Standard Java serializasion magic number and version.
  • 77 (TC_BLOCKDATA): Denotes block data containing method metadata, object IDs, and interface hashes.

Directly modifying these headers is difficult due to built-in hash verification. Successful exploitation generally requires matching the server's expected method hash or targeting fallback deserialization pathways such as DGC (Distributed Garbage Collecsion) or the Activation system.

Automated Attack Workflow with RMG

The Remote Method Guesser (RMG) framework automates RMI reconnaissance, method discovery, and payload delivery. It streamlines the exploitation process by handling protocol formatting, hash verification, and gadget injection.

Enumeration Phase

Executing rmg enum [target-host] probes the registry for bound object names, exposed codebases, and configuration weaknesses. The tool identifies missing Security Managers, outdated JEP-290 filters, and deserialization behaviors, providing a clear map of available attack surfaces.

Method Discovery

RMI verifies remote calls using cryptographic hashes derived from method signatures. RMG includes a brute-force module (rmg guess) that tests dictionaries of method names and parameter types against the target hash. A successful match confirms a valid endpoint for payload injection.

Security researchers can compute valid method hashes locally using reflection and SHA-1 digests:

public static long calculateMethodSignatureHash(Method targetMethod) {
    try {
        MessageDigest digest = MessageDigest.getInstance("SHA-1");
        StringBuilder signatureBuilder = new StringBuilder(targetMethod.getName()).append('(');
        for (Class> paramType : targetMethod.getParameterTypes()) {
            signatureBuilder.append(mapTypeToDescriptor(paramType));
        }
        signatureBuilder.append(')').append(mapTypeToDescriptor(targetMethod.getReturnType()));

        byte[] rawHash = digest.digest(signatureBuilder.toString().getBytes(StandardCharsets.UTF_8));
        long result = 0;
        for (int i = 0; i < Math.min(8, rawHash.length); i++) {
            result += (long)(rawHash[i] & 0xFF) << (i * 8);
        }
        return result;
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Hash algorithm unavailable", e);
    }
}

private static String mapTypeToDescriptor(Class> type) {
    if (type == int.class) return "I";
    if (type == boolean.class) return "Z";
    if (type == void.class) return "V";
    return type.isArray() ? "[" + type.getComponentType().getName().replace('.', '/') : "L" + type.getName().replace('.', '/') + ";";
}

Payload Delivery

Once a valid method is identified, RMG injects deserialization payloads via the serial command. While standard gadgets like CommonsCollections6 are commonly utilized, security testers often modify the integration layer to supply custom chains or bypass application-layer filters. The tool formats the malicious object within the JRMP stream, ensuring it aligns with the discovered method's hash expectation.

Codebase and Remote Class Loading

RMI supports dynamic class downloading via codebase URLs. If the server permits external code loading (java.rmi.server.useCodebaseOnly=false) and lacks strict security policies, attackers can host malicious classes on an external HTTP server. During deserialization, the victim JVM fetches and instantiates the remote class, leading to immediate code execution. This vector remains highly dependent on legacy configurations and modern JVM restrictions.

Tags: java-rmi JRMP deserialization rmg penetration-testing

Posted on Sat, 07 Nov 2026 16:10:22 +0000 by mrkumar