Java RMI and JRMP deserialization explained

Java RMI and JRMP deserialization explained

Written by

in

A java rmi deserialization flaw is what makes every other gadget chain far more dangerous. Java Remote Method Invocation lets one JVM call methods on objects living in another JVM, and it moves those method arguments over the network as serialized Java objects. The receiving side deserializes them. If an attacker can reach that endpoint, they can send a crafted object graph and trigger any gadget chain on the classpath, with no login required. This post explains the wire protocol underneath, why it is exposed, and how to lock it down.

Why java rmi deserialization is a remote, unauthenticated trigger

Most deserialization bugs need the attacker to find a spot where your app reads untrusted bytes. RMI hands them that spot for free. The protocol’s entire job is to accept serialized objects off a socket and rebuild them. The low level protocol, JRMP (Java Remote Method Protocol), carries the serialized arguments. When the server receives a call, it deserializes those arguments before any of your business logic runs.

That means the dangerous readObject step happens at the network layer, before authentication, before any check you wrote. An attacker who can open a TCP connection to the RMI port can make the server deserialize whatever they send. If you are new to the underlying issue, read our primer on what insecure deserialization is and the hub on what a deserialization gadget chain is.

The pieces: registry, stubs, and the wire

A typical RMI setup has a few moving parts:

  • An RMI registry, usually on port 1099, that maps names to remote objects.
  • Remote objects that expose methods a client can call.
  • The JRMP transport that serializes call arguments on the client and deserializes them on the server.

You register a service like this:

Registry registry = LocateRegistry.createRegistry(1099);
registry.rebind("notes", new NotesServiceImpl());

A client looks it up and calls a method. Behind that clean API, the arguments travel as a serialized object stream, and the server rebuilds them. The registry itself is also a remote object that deserializes input, so even the lookup path is an attack surface.

How an attacker uses it

The attacker does not need to call a real method correctly. They only need the server to deserialize their bytes. The flow is blunt:

  • Connect to the exposed RMI port.
  • Send a JRMP message whose payload is a serialized object graph built as a gadget chain.
  • The server deserializes the arguments, the chain fires during reconstruction, and code runs.

RMI was designed in an era that assumed the network was trusted. The protocol deserializes attacker controlled objects before your code gets a say, so exposing it to an untrusted network is exposing a readObject call to the internet.

The payload itself is any chain that works on the target’s classpath. It might be a Commons Collections gadget chain, or a different set of gadgets. RMI does not care. It is the transport, and the transport is the easy part for the attacker. The way those gadgets are ordered into a working payload is covered in pop chains and property oriented programming. Public gadget chain toolkits exist that assemble these payloads, which is why an exposed RMI port is treated as high risk even when no one can name the exact chain in advance.

JRMP as a second stage

There is a sharper variant worth knowing. Some gadget chains do not run a command directly. Instead they make the victim JVM open a JRMP connection back to an attacker controlled server. That server then streams a malicious object back, and the victim deserializes it. So even an app that does not expose RMI to receive calls can be pushed into acting as a JRMP client through another deserialization bug. The lesson is that RMI and JRMP are not just an inbound service to protect. They are a deserialization sink and a deserialization source at the same time.

How to lock down java rmi deserialization

The strongest move is to not expose RMI at all. Most modern services have no reason to speak it over an untrusted network.

  • Do not expose RMI to untrusted networks. If an internal tool uses it, bind it to localhost or a private segment and firewall the port. An RMI registry reachable from the internet is almost always a mistake.
  • Apply a deserialization filter. Java’s ObjectInputFilter can be set globally through the jdk.serialFilter system property so that even the RMI and registry layers reject classes outside a strict allowlist. Recent JDKs ship a built in filter for the RMI registry; keep it enabled and tighten it.
# Restrict deserialization across the JVM, including RMI
-Djdk.serialFilter=java.base/*;!*
  • Prefer a modern transport. Replace RMI with an API that uses typed, data only messages, such as a REST or gRPC service with explicit schemas and no native object deserialization.
  • Trim the classpath. Fewer gadget classes present means fewer chains a payload can complete, the same defense that helps everywhere else.

Network isolation and a strict filter together cut the attack off at both ends: the attacker cannot reach the port, and even if they could, the filter refuses to build the classes a chain needs.

The same root cause, a worse blast radius

Every post in this cluster comes back to one idea: untrusted bytes becoming live objects. What RMI adds is reach. A SnakeYAML deserialization bug or an XStream deserialization bug usually needs an endpoint in your app that parses the format. RMI is the endpoint, built in, and it listens on a port. That is why an exposed RMI service is often the first thing worth checking on a Java host.

This is an injection class problem, untrusted input reaching a sink that was never meant for it. For more in that family, see our injection and input category.

Finding this kind of exposure means tracing untrusted network input through the RMI transport to the readObject call that runs before your code, and checking whether a filter stands in the way. Reasoning from source to sink about untrusted input reaching a deserializer is exactly what UnboundCompute is built to do. More on our about page.

Frequently asked questions

What makes Java RMI deserialization so dangerous?

RMI moves method arguments over the network as serialized Java objects and deserializes them on the server before any authentication or business logic runs. An attacker who can reach the RMI port can send a crafted object graph and trigger any gadget chain on the classpath without logging in.

What is JRMP?

JRMP is the Java Remote Method Protocol, the low level wire protocol RMI uses to carry serialized call arguments. It is the transport that performs the readObject step on attacker controlled bytes, which is why it is the real sink behind RMI attacks.

Can an app be attacked through JRMP even if it does not expose RMI?

Yes. Some gadget chains make the victim JVM open a JRMP connection back to an attacker server, which then streams a malicious object that the victim deserializes. So RMI and JRMP act as both a deserialization sink and a deserialization source.

How do I secure RMI endpoints?

Do not expose RMI to untrusted networks; bind it to localhost or a private segment and firewall the port. Apply a strict ObjectInputFilter through jdk.serialFilter so even the RMI and registry layers reject classes outside an allowlist, prefer a typed data only transport instead, and trim the classpath so fewer gadgets are available.


Put an autonomous researcher on your own systems

UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.