Java Deserialization Exploits: Registry Whitelist Bypass

Circuit board with digital lock symbol.

In 2019, An Trinh discovered two vulnerabilities, CVE-2019-9670 (XXE/SSRF) and CVE-2019-6980 (deserialization vulnerability), in Zimbra.

As usual, An Trinh did not disclose any details.

Luckily, Hans Martin Munch is more generous than An Trinh and has shared many interesting ideas. For example, he once advised using YouDebug to fix the CVE-2017-3241 vulnerability.

ysoserial.payloads.JRMPClient is designed to trick a victim into accessing a malicious DGC server as a DGC client. When the victim deserialization comes from a malicious object of the DGC server, a filter is configured by default. For details, see the implementation of sun.rmi.transport.DGCImpl.checkInput().

A new idea proposed by An Trinh is to trick a victim into accessing a malicious RMI Registry server as an RMI Registry client. In this case, there is no filter involved if the victim deserialization comes from a malicious object of the RMI Registry server. No default filter is configured on JEP 290 for this scenario.

In this document, OpenJDK 8u232 is used for demo purposes.

(1) RMIRegistryServer.java

Red circular no entry sign with a white horizontal bar.

(2) EvilRMIRegistryClientWithUnicastRemoteObjectFail.java

Red circular no entry sign with a white horizontal bar.

Run the following command to start the malicious service:

Red circular no entry sign with a white horizontal bar.

Run the following command to start the victim’s machine:

Red circular no entry sign with a white horizontal bar.

Run the following command to start the attacker’s machine:

Red circular no entry sign with a white horizontal bar.

This attack fails to achieve the desired objective.

a. Failure Cause

References:

http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/registry/RegistryImpl_Stub.java
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/server/MarshalOutputStream.java
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/ObjectTable.java
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/Target.java
Red circular no entry sign with a white horizontal bar.

If UnicastRemoteObject.exportObject() has been called, when the ObjectOutputStream.writeObject() serialization is called to output the UnicastRemoteObject instance, MarshalOutputStream.replaceObject() will be triggered to replace the UnicastRemoteObject instance with another object instance, thereby compromising the attack chain.

To avoid such replacement, reflection can be used to change the value of ObjectOutputStream.enableReplace from “true” to “false”. This is Hans Martin Munch’s advice.

b. Using YouDebug

According to endnote [3], YouDebug allows the execution of automatic debugging. Breakpoints and actions upon a breakpoint hit can be defined in the script in advance.

Edit ModifyRebind.ydb as follows:

Red circular no entry sign with a white horizontal bar.

The intention of the script is simply to block ObjectOutputStream.writeObject(). If the type of the object is UnicastRemoteObject, the value of ObjectOutputStream.enableReplace will be changed from “true” to “false”.

Run the following command to start the malicious service:

Red circular no entry sign with a white horizontal bar.

Run the following command to start the victim’s machine:

Red circular no entry sign with a white horizontal bar.

Run the following command to start the attacker’s machine:

Red circular no entry sign with a white horizontal bar.

(3) Customizing RegistryImpl_Stub.rebind()

Hans Martin Munch suggests the customization of RegistryImpl_Stub.rebind(), namely, modification of ObjectOutputStream.enableReplace via reflection before invoking writeObject(). He left this as homework, without providing the answer directly.

Red circular no entry sign with a white horizontal bar.

(4) Simplified Calling Relationships After a Successful Attack

References:

http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/registry/RegistryImpl_Skel.java
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/server/UnicastRef.java
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/tcp/TCPChannel.java
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/StreamRemoteCall.java
Red circular no entry sign with a white horizontal bar.

Local attackers can successfully launch an attack against OpenJDK 8u232. However, remote attackers, when launching an attack against OpenJDK 8u232, cannot pass the source IP address check predefined in RegistryImpl_Skel:142.

As indicated on page 20 of endnote 1, the stack top of the invoked stack is as follows:

sun.rmi.server.UnicastRef.unmarshalValue()

sun.rmi.transport.tcp.TCPChannel.newConnection()

sun.rmi.server.UnicastRef.invoke()

In my opinion, this is a forged stack provided by An Trinh. TCPChannel.newConnection() has nothing to do with the attack chain. UnicastRef.unmarshalValue() can be exploited, but StreamRemoteCall.executeCall() has been exploited for attack, as shown in the preceding figure.

(5) Hans Martin Munch’s Misjudgment

Hans Martin Munch made a detour by changing the value of ObjectOutputStream.enableReplace, there is a misjudgment. If he had studied ysoserial.payloads.JRMPListener provided by Matthias Kaiser, he could be more direct.

According to Hans Martin Munch’s solution, the call stack trace is as follows:

[1] sun.rmi.transport.ObjectTable.putTarget (ObjectTable.java:171), pc = 0
[2] sun.rmi.transport.Transport.exportObject (Transport.java:106), pc = 6
[3] sun.rmi.transport.tcp.TCPTransport.exportObject (TCPTransport.java:265), pc = 32
[4] sun.rmi.transport.tcp.TCPEndpoint.exportObject (TCPEndpoint.java:411), pc = 5
[5] sun.rmi.transport.LiveRef.exportObject (LiveRef.java:147), pc = 5
[6] sun.rmi.server.UnicastServerRef.exportObject (UnicastServerRef.java:237), pc = 78
[7] java.rmi.server.UnicastRemoteObject.exportObject (UnicastRemoteObject.java:383), pc = 19
[8] java.rmi.server.UnicastRemoteObject.exportObject (UnicastRemoteObject.java:320), pc = 9
[9] java.rmi.server.UnicastRemoteObject. (UnicastRemoteObject.java:198), pc = 26
[10] java.rmi.server.UnicastRemoteObject. (UnicastRemoteObject.java:180), pc = 2
[11] sun.reflect.NativeConstructorAccessorImpl.newInstance0 (native method)
[12] sun.reflect.NativeConstructorAccessorImpl.newInstance (NativeConstructorAccessorImpl.java:62), pc = 85
[13] sun.reflect.DelegatingConstructorAccessorImpl.newInstance (DelegatingConstructorAccessorImpl.java:45), pc = 5
[14] java.lang.reflect.Constructor.newInstance (Constructor.java:423), pc = 79

UnicastRemoteObject.exportObject() will trigger ObjectTable.putTarget(),

while the ObjectOutputStream.writeObject() has to go through the following functions during deserialization of the ObjectOutputStream.writeObject() instance:

http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/server/MarshalOutputStream.java
Red circular no entry sign with a white horizontal bar.

After the attack chain is broken, the malicious object will not be sent to the victim.

The new whitelist bypass technique is to generate a UnicastRemoteObject instance like ysoserial.payloads.JRMPListener, so as to prevent UnicastRemoteObject.exportObject() from being triggered on the client side to invoke ObjectTable.putTarget(). In this way, the value of “target” returned by MarshalOutputStream:81 is “null” and no replacement occurs.

(6) Not Bypassing Source IP Address Check

The new technique put forward by An Trinh can only be used to launch local attacks. If rmiregistry is started by a root user and a local attacker only has common user privileges, the method provided in this document can help the local attacker to obtain root privileges illegally. However, it does not work for remote attackers.

In OpenJDK 8u232, the code at RegistryImpl_Skel:142 is:

RegistryImpl.checkAccess("Registry.rebind")

It checks whether the source IP address of rebind() is the IP address of the local device. If not, the readObject() function will not be invoked. It is said that OpenJDK 8u232 has been preconfigured with source IP address check.

From the perspective of prevention, it is advisable to use the latest version of Java where conditions permit.

References:

[1] Far Sides of Java Remote Protocols – An Trinh [2019-12-04]

https://www.blackhat.com/eu-19/briefings.html
http://i.blackhat.com/eu-19/Wednesday/eu-19-An-Far-Sides-Of-Java-Remote-Protocols.pdf

[2] An Trinhs RMI Registry Bypass – Hans Martin Munch [2020-02]

https://mogwailabs.de/blog/2020/02/an-trinhs-rmi-registry-bypass/

[3] YouDebug

https://github.com/kohsuke/youdebug
NSFOCUS
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.