Java-RMI
Introduction
RMI is Remote Method Invocation, which offers a remote interface as Java’s RPC framework to the application.
Procedure
The procedure can be represented as the following graph.
Code Analysing
DEMO
First, let’s write a RMI demo.
The java env is jdk8u65
Server
1 | //RemoteObj |
1 | //RemoteObjImpl |
1 | //RemoteServer |
Client
1 | //RMIClient |
Create a RemoteObj
1 | RemoteObj remoteObj = new RemoteObjImpl(); |
It seems a simple instantiation, but remember that we have extended the UnicastRemoteObject
.
So let’s see what happened inside.
Firstly, we come into the UnicastRemoteObject#exportObject
.
We can see this method published a Remote
obj on the server(port: 0 means a random port).
Here, the UnicastServerRef
encapsulates various network information.
From my opinion, UnicastServerRef
is something to deal with the stub while LiveRef
is to deal with the Internet.
Then, our object will be encapsulated into the UnicastServerRef
too.
Then, UnicastServerRef
create a stub for the Object, and encapsulates the impl
and stub
and something into a whole Target
Then follows another exportObject.
Finally, the server will listen to the port and handle the requests.
At last, there is a another exportObject
Let’s see what’s in the ObjectTable.putTarget
The target will finally be put into the objTable
and implTable
That means we can lookup the target by ObjectEndpoint
or Implementation
Register the RemoteObj
Actually, the Target
has a random port, which is unfriendly to the client.
So we can create a registry on a specific port and bind the RemoteObj.
Create a Registry
First, let’s see how to create a registry.
First we create a LiveRef
encapsulated with the network information.
Then we encapsulate the LiveRef
into the UnicastServerRef
and call the setup
method.
where call the UnicastServerRef#exportObject
again.
But this time, we enter into the setSkeleton
Here we use reflection to create RegistryImpl_Skel
, which implements every operation in register.
and finally, the stub will be encapsulated as a Target
and exported.
But there are three stuff in the objTable.
Two are what we created, the other is DGC, distributed garbage-collection
bind
It’s very easy, just put the name and RemoteObj into the Hashtable.
Visit Registry to fetch the RemoteObj
From Client View
What happened when the client try to connect the registry.
Finally, we can find we create a proxy of the Registry.
Then let’s check about the registry.lookup
Here, we get the Registry’s dynamic proxy Stub, and deserialize the return value.
In this decompiled file, we can’t make breakpoint, but we can still follow the program.
Follow the invoke() —> call.executeCall()
We can find a deserialize code here.
That means, if the registry returns a a malicious class, it is possible to create a hidden deserialization vulnerability on the client.
And once the client call the invoke
, it may cause the vulnerability.
From Registry View
make a breakpoint in Transport#serviceCall
We can see the Registry receives a stub, which includes a ref.
Then the skel
will be put into the disp
and call disp.dispatch()
Then we go into the oldDispatch
Following the skel.dispatch()
op represents the client’s operation.
1 | 0->bind |
So, in RegistryImpl_Skel#dispatch
, there are some case code block, and some of them are vulnerable, which means the hacker can attack the registry from client.
bind
lookup
rebind
unbind
In our lookup
request, it will finally go into the lookup
method, and get the RemoteObj
from bindings
, and return a serialization value.
Call Remote Method
From Client View
We should remember that the remoteObj we received is a Proxy, so what happened when we called remoteObj.sayHello
at the client side?
Following the code, we can find we finally called UnicastRef#invoke
to call remote method.
Here we serialize our parameters, and call a dangerous method call.executeCall()
Besides, the unmarkedshalValue
method here is also dangerous. Because it can Deserialize a malicious class.
From Server View
As the last section, we make a breakpoint in Transport#serviceCall
Same as before, we go into the UnicastServerRef#dispatch
But this time, we will not call the oldDispatch
And the method here is the sayHello()
But next, we can see the unmarshalValue
method here, which means there exists a deserialization vulnerability.
And finally, server will make a reflection call and pack the response.
DGC
DGC is an automatically created class to clean up memory.
As we metioned in 3.3.1, we also put a DGCImpl_Stub
into the objTable
Like Registry at client side, we will also create a DGCImpl_Stub
However, there both exists Deserialization in DGCImpl_Stub#dirty
and DGCImpl_Skel#dispatch
So it is also vulnerable.
Attack Method
Attack Client
Registry attack Client
As we discussed before, if we call the RegistryImpl_Stub#lookup
, it will deserialize the data from the registry.
Therefore, here is vulnerable and we can use ysoserial
to generate the payload.
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 'open -a calculator'
Besides, RegistryImpl_Stub#bind
, RegistryImpl_Stub#list
can also trigger this vulnerability.
Server attack Client
When the server returns an object to the client, the client has to deserialize it . So if we returned a evil malicious object, the client will be attack.
Remote Class Loading
Server returns a non-existent class to Client, requiring Client to go to codebase address to remotely load malicious class triggers vulnerability.
The conditions for such exploit are very harsh and the availability is not strong.
Attack Server
malicious parameter
A deserialization vulnerability can be triggered by a malicious Client passing in a malicious data stream when the invoked method on the Server side has parameters of a non-basic type.
If the server ask to receive a specific type, we can use some way to bypass the limitation.
Remote Class Loading
Attack Registry
If we are a malicious Server side that delivers a malicious object to the Registry side (Server and Registry are not on the same host), a malicious call can be triggered on its deserialization.
But, the class we bind must extends Remote
class.
So we can use AnnotationInvocationHandler
to create a dynamic proxy of Remote.
1 | AnnotationInvocationHandler.invoke() |
1 | public class RemoteServer { |
Reference
https://su18.org/post/rmi-attack/
https://drun1baby.top/2022/07/19/Java反序列化之RMI专题01-RMI基础/
https://drun1baby.top/2022/07/23/Java反序列化之RMI专题02-RMI的几种攻击方式/