In Java deserialization vulnerability mining or exploitation, we often encounter such concepts as RMI, JNDI and jrmp, where RMI is a Java Remote method call mechanism based on serialization. As a common deserialization entry, it is closely related to deserialization vulnerability. In addition to directly attacking the RMI service interface (such as cve-2017-3241), we can also combine RMI to facilitate remote code execution when constructing deserialization vulnerability exploitation.
This paper tries to introduce the RMI mechanism and JNDI injection utilization mode in a simple and understandable way, and takes the use of chain and fastjson deserialization vulnerability of jdbcrowsetimpl as an example to record the problems and solutions that may be encountered in the real remote utilization process, hoping to give some reference to the new students studying this field. If there is any error, welcome to exchange and correct.
This section mainly introduces the call process of RMI, RMI registry and the concept of dynamic loading classes.
1.1 remote method call
1.2 remote objects
The use of remote method calls will inevitably involve the passing of parameters and the return of execution results. The parameter or return value can be of basic data type, of course, it can also be a reference to an object. Therefore, the objects to be transmitted must be serializable, which requires that the corresponding class must implement the java.io.serializable interface, and the serialVersionUID field of the client side should be consistent with that of the server side.
When communicating between JVMs, RMI processes remote objects and non remote objects differently. Instead of copying a remote object directly to the client, RMI passes a stub of the remote object, which is basically the reference or agent of the remote object. Stub is transparent to developers, and clients can call remote methods directly through it just as they call local methods. Stub contains the location information of remote objects, such as socket port, server host address, etc., and implements the specific underlying network communication details in the remote call process. Therefore, RMI remote call logic is as follows:
Logically, data flows horizontally between client and server, but in fact, it flows vertically from client to stub, and then from skeleton to server.
The server side listens for a port, which is randomly selected by the JVM;
The client side does not know the communication address and port of the server remote object, but stub contains these information and encapsulates the underlying network operations;
Client side can call methods on stub;
Stub connects to the communication port monitored by the server and submits parameters;
Execute the specific method on the remote server and return the result to stub;
Stub returns the execution result to the client. From the client's point of view, it looks like stub executed this method locally;
How to get stubs?
1.3 RMI registry
There are many ways to get stubs. The common way is to call a method on a remote service to get stubs from the remote service. But to call a remote method, you must first have the stub of the remote object, so there is a dead loop problem here. JDK provides an RMI registry to solve this problem. Rmiregistry is also a remote object. By default, it listens on the legendary 1099 port. You can use code to start rmiregistry or rmiregistry command.
To register a remote object, you need the RMI URL and a reference to the remote object.
LocateRegistry. Getregistry() creates a stub object locally with the given host and port information as the proxy of the registry remote object, thus starting the whole remote call logic. The server application can register the remote object in the RMI registry, and then the client queries the RMI registry for the name of a remote object to obtain the stub of the remote object.
After using RMI registry, the calling relationship of RMI is as follows:
In fact, from the perspective of the client, the server application has two ports: one is the RMI registry port (1099 by default), and the other is the communication port of the remote object (randomly assigned). This communication detail is quite important, and there may be some holes in the real use process.
1.4 dynamic loading class
This concept is more important, and the use of JNDI injection also relies on the idea of dynamically loading classes.
Roles involved here: client, RMI registry, remote object server, web server hosting class file can be on different hosts respectively:
2.1 about JNDI
In short, JNDI (Java Naming and Directory Interface) is a set of application program interfaces. It provides a unified and universal interface for developers to find and access various resources, which can be used to locate users, networks, machines, objects, services and other resources. For example, you can use JNDI to locate a printer on a local area network, or you can use JNDI to locate a database service or a remote Java object. The bottom layer of JNDI supports RMI remote objects, and services registered by RMI can be accessed and called through JNDI interface.
JNDI supports a variety of naming and directory providers, and RMI registry service provider allows access to remote objects registered in RMI through JNDI application interface. One of the advantages of binding RMI service to JNDI is that it is more transparent, unified and loosely coupled. RMI clients directly locate a remote object through a URL, and the RMI service can be linked with an enterprise directory containing information such as personnel, organization and network resources.
During the initialization of JNDI interface, RMI URL can be passed in as a parameter, and JNDI injection appears in the client's lookup() function. If the parameter of lookup() is controllable, it may be attacked.
Note: initialcontext is a class that implements the context interface. Use this class as the entry point for the JNDI naming service. To create the initialcontext object, you need to pass in a set of properties with the parameter type of java.util.hashtable or one of its subclasses.
2.2 injection with JNDI references
In the JNDI service, the RMI server can bind an external remote object (object other than the current name directory system) through the references class in addition to the remote object directly. After the reference is bound, the server first obtains the reference of the bound object through referenceable. Getreference(), and saves it in the directory. When the client looks up the remote object in lookup(), the client will obtain the corresponding object factory, and finally convert the reference to a specific object instance through the factory class.
InitialContext.lookup (URI) is invoked in the target code, and URI is controlled by the user.
The attacker controls the URI parameter as a malicious RMI service address, such as RMI: / / hacker_rmi_server / / name;
The attacker RMI server returns a reference object to the target, which specifies a specially constructed factory class;
When the target performs the lookup () operation, it will dynamically load and instantiate the factory class, and then call factory. Getobjectinstance() to get the external remote object instance;
Attackers can write malicious code in the construction method, static code block, getobjectinstance() method of the factory class file to achieve the effect of rce;
Here, the attack target plays the role of JNDI client. The attacker builds a malicious RMI server to carry out the attack. When we follow the code of the lookup() function, we can see the processing logic of reference class in JNDI, and finally call namingmanager. Getobjectinstance():
Call chain:
2.3 use of fastjson deserialization
The attacker's server needs to start an RMI registry, bind a reference remote object, and set a malicious factory class.
Many POCS on the Internet are tested locally. However, in the process of remote utilization, some pits may be encountered, which will directly lead to utilization failure, such as the error of timeout.
3.1 why does remote utilization cause timeout?
When using JNDI to inject payload for utilization, sometimes it is found that the target is indeed connected to our RMI server, but does not download the malicious class file on the webserver. We use Kali as the attacker's RMI server in the local area network to repeat the attack process. We often see a timeout error prompt like this:
Why does it time out?
In fact, as we mentioned in the first section, there are two ports on the server that started RMI registry, one is the RMI registry listening port, and the other is the communication port of the remote object. The communication port of the remote object is randomly assigned by the system. The communication host, port and other information of the remote object are transmitted to the client by RMI registry. The default value of the communication host is the IP address corresponding to the local host name of the server.
As you can see, the Kali hostname is resolved to 127.0.1.1 by default. We can restore this communication details by capturing packets:
Note: the attacker RMI server sends the location information of the remote object to the target
Note: it's easy to solve the problem that the target sends a request to the attacker's remote object. You can delete the records pointing to the intranet IP in / etc / hosts or point to the Internet IP. You can also specify the remote object communication host IP through the code on the RMI server of the attacker: