Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to understand RMI JRMP and multiple uses of JNDI in the process of Java deserialization

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >

Share

Shulou(Shulou.com)05/31 Report--

How to understand the Java deserialization process of RMI JRMP and JNDI in a variety of ways, many novices are not very clear about this, in order to help you solve this problem, the following editor will explain in detail for you, people with this need can come to learn, I hope you can get something.

The preface of multiple uses of RMI JRMP and JNDI in the process of Java deserialization

Java deserialization vulnerabilities have always been difficult to understand in Java Web vulnerabilities, especially when it comes to the concepts of RMI and JNDI. According to the explanations in all kinds of related articles on the Internet, and combined with my understanding of the concepts of RMI JRMP and JNDI, the author introduces in detail the relationship among RMI client, server, rmiregistry and various attacks among the three parties. I hope it will be helpful for readers to learn Java Web security.

Brief introduction to the principle of RPC Framework

First of all, before talking about these, we should understand a concept. All the high-level concepts in programming, some functions that seem to be very advanced, are based on the most basic code, and no matter how advanced they are, they can't do without JDK.

For example, the concept of distribution involved this time is implemented through java's socket, serialization, deserialization and reflection.

For example, if the client wants to call the A method of the An object on the server side, the client will generate the proxy object of the An object, and the proxy object establishes a connection with the server by using Socket, and then serializes the A method and the parameters to be passed in to the server through socket, and the server accepts the data received by deserialization, and then calls the A method of the An object and passes in the parameters through reflection. Finally, the execution result is returned to the client, giving the illusion that the client has called the A method of the An object on the server side locally.

Source code analysis of RMI process

In the end, JAVA RMI was no exception, but in order to facilitate more flexible calls, it developed into the following

There is another party C between the client (remote method caller) and the server (remote method provider), which is called Registry, which is the registry.

The code to start this registry is very simple, as follows

This Registry is a separate program path located at / Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/bin/rmiregistry

The code just shown to start RMIRegistry simply calls the rmiregistry executable.

Simply follow the code

Public static Registry createRegistry (int port) throws RemoteException {return new RegistryImpl (port);} public RegistryImpl (final int var1) throws RemoteException {this.bindings = new Hashtable; if (var1 = = 1099 & & System.getSecurityManager ()! = null) {try {.} else {LiveRef var2 = new LiveRef (id, var1); this.setup (new UnicastServerRef (var2, RegistryImpl::registryFilter));}}

Simply follow the code

Public static Registry createRegistry (int port) throws RemoteException {return new RegistryImpl (port);} public RegistryImpl (final int var1) throws RemoteException {this.bindings = new Hashtable; if (var1 = = 1099 & & System.getSecurityManager ()! = null) {try {.} else {LiveRef var2 = new LiveRef (id, var1); this.setup (new UnicastServerRef (var2, RegistryImpl::registryFilter));}}

It's simple. There's nothing but four attributes in liveRef.

Public class LiveRef implements Cloneable {/ / points to a TCPEndpoint object, the ip address and port number of the specified Registry, private final Endpoint ep;//, a currently unknown id number, private final ObjID id;//, nullprivate transient Channel ch;//, trueprivate final boolean isLocal;.}.

This.setup (new UnicastServerRef (var2, RegistryImpl::registryFilter)); there is a parameter RegistryImpl::registryFilter in this paragraph. The registryFilter added after the jdk1.8.121 version is specially used to verify the deserialized classes passed in. Classes that are not in the deserialization whitelist are not allowed to deserialize. The specific method code is as follows

Private static Status registryFilter (FilterInfo var0) {if (registryFilter! = null) {Status var1 = registryFilter.checkInput (var0); if (var1! = Status.UNDECIDED) {return var1;}} if (var0.depth () > 20L) {return Status.REJECTED;} else {Class var2 = var0.serialClass () If (var2! = null) {if (! var2.isArray ()) {/ / you can clearly see that the following nine types can be deserialized return String.class! = var2 & &! Number.class.isAssignableFrom (var2) & &! Remote.class.isAssignableFrom (var2) & &! Proxy.class.isAssignableFrom (var2) & &! UnicastRef.class.isAssignableFrom (var2) & &! RMIClientSocketFactory.class.isAssignableFrom ( Var2) & &! RMIServerSocketFactory.class.isAssignableFrom (var2) & &! ActivationID.class.isAssignableFrom (var2) & &! UID.class.isAssignableFrom (var2)? Status.REJECTED: Status.ALLOWED;} else {return var0.arrayLength () > = 0L & & var0.arrayLength () > 1000000L? Status.REJECTED: Status.UNDECIDED;}} else {return Status.UNDECIDED;}

The whitelist will be put aside for a while and will be used later. After executing new UnicastServerRef (var2, RegistryImpl::registryFilter), take a brief look at the contents of the UnicastServerRef object.

Setup method content

Private void setup (UnicastServerRef var1) throws RemoteException {this.ref = var1;var1.exportObject (this, (Object) null, true);

UnicastServerRef.exportObject () method content

Public Remote exportObject (Remote var1, Object var2, boolean var3) throws RemoteException {/ / gets the class object Class var4 = var1.getClass () of RegistryImpl; the value returned by Remote var5;try {/ / Util.createProxy is RegistryImpl_Stub, and this stub will explain var5 = Util.createProxy (var4, this.getClientRef (), this.forceStubUse);} catch (IllegalArgumentException var7) {throw new ExportException ("remote object implements illegal remote interface", var7) } / / RegistryImpl_Stub inherits from RemoteStub to determine whether if (var5 instanceof RemoteStub) {/ / assigns a value to Skeleton, and assigns it through this.skel = Util.createSkeleton (var1). The final result returned by Util.createSkeleton (var1) is a RegistryImpl_Skel object, and this Skeleton will also say this.setSkeleton (var1);} / / instantiate a Target object Target var6 = new Target (var1, this, var5, this.ref.getObjID (), var3) / / bind the information about stub in this target object this.ref.exportObject (var6); this.hashToMethod_Map = (Map) hashToMethod_Maps.get (var4); / / eventually LocateRegistry.createRegistry (1099) will return a RegistryImpl_Stub object / / start rmiregistry at the same time and listen on the specified port return var5;}

Very well, the process of starting rmiregistry is simply analyzed, but there is a question at this time, that is, why is a registration mechanism like rmiregistry needed? Wouldn't it be nice to call each other directly through Socket between the client and the server? Just like the code of teacher Ma Bing. It is obvious that it is just a simple use case that explains the principle, and it will certainly not be so simple in the actual production environment.

First, take a look at the following simple flow chart of RMI

Before considering why you need this rmiregistry, think about a more awkward question. If the client (the remote method caller) wants to call the server (the remote method server), how can the client know the ip address and port number that the server uses to provide the remote method invocation service? You said just discuss it in advance and write it in the code? However, the port numbers provided by the server are random, so we can't manually specify a new port number every time we add a new remote method providing class to the server.

So now it is very awkward and falls into an endless loop. If the client wants to call the method of the server, the client needs to know the address of the server and the corresponding port number, but the client does not know because no one has told him. So it's quite a headache.

At this point, there is such a thing as rmiregistry. We first call rmiregistry Party C, and the function is very simple. Each new remote method provided by the server will come to Party C (rmiregistry) to register and write down the ip address and the corresponding port and other information that provides the remote message service of this method.

As the following code shows, first if we want to write a class that provides a remote method invocation service, we first write an interface and inherit the Remote interface

Public interface IHello extends Remote {/ / sayHello is the method to be called by the client. You need to throw RemoteExceptionpublic String sayHello () throws RemoteException;}

Then write a class to implement the interface

The package com.rmiTest.IHelloImpl;import com.rmiTest.IHello;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;// class can choose to inherit UnicastRemoteObject, or through this form in the comments below, it is essentially the same as calling the / / exportObject () method / / Remote remote = UnicastRemoteObject.exportObject (new HelloImpl ()); / / LocateRegistry.getRegistry ("127.0.0.1", 1099). Bind ("hello", remote) Public class HelloImpl extends UnicastRemoteObject implements IHello {public HelloImpl () throws RemoteException {} @ Overridepublic String sayHello () {System.out.println ("hello"); return "hello";}}

Finally, register the HelloImpl class to, or bind to, rmiregistry, that is, Party C.

Package com.rmiTest.provider;import com.chouXiangTest.impl.HelloServiceImpl;import com.rmiTest.IHelloImpl.HelloImpl;import java.rmi.AlreadyBoundException;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;public class RMIProvider {public static void main (String [] args) throws RemoteException, AlreadyBoundException {LocateRegistry.getRegistry ("127.0.0.1", 1099). Bind ("hello", new HelloImpl ());}}

First of all, let's follow the instantiation process of HelloImpl, a remote object. First, HelloImpl is a subclass of UnicastRemoteObject, so HelloImpl will first call the constructor of the UnicastRemoteObject class when instantiating, which is as follows

Protected UnicastRemoteObject (int port) throws RemoteException {/ / this prot parameter is used to specify the port corresponding to the remote method, which is random by default, or you can manually pass in the parameter to specify this.port = port;exportObject ((Remote) this, port);}

Find that it will call an exportObject method and continue to follow this method.

Private static Remote exportObject (Remote obj, UnicastServerRef sref) throws RemoteException {/ / if obj extends UnicastRemoteObject, set its ref.if (obj instanceof UnicastRemoteObject) {(UnicastRemoteObject) obj). Ref = sref;} return sref.exportObject (obj, null, false);}

Continue to follow up on the UnicastServerRef.exportObject method with the following internal code

Public Remote exportObject (Remote var1, Object var2, boolean var3) throws RemoteException {/ / gets HelloImpl's class object Class var4 = var1.getClass (); Remote var5;try {/ / this step is to create a proxy object, which implements the IHello interface, using Handler RemoteObjectInvocationHandlervar5 = Util.createProxy (var4, this.getClientRef (), this.forceStubUse);} catch (IllegalArgumentException var7) {throw new ExportException ("remote object implements illegal remote interface", var7);} if (var5 instanceof RemoteStub) {this.setSkeleton (var1) } Target var6 = new Target (var1, this, var5, this.ref.getObjID (), var3); this.ref.exportObject (var6); this.hashToMethod_Map = (Map) hashToMethod_Maps.get (var4); return var5;}

The result returned by the Util.createProxy () method is shown below

Continue to follow this.ref.exportObject (var6), after a series of nested calls, and finally come to the exportObject method of TCPTransport, which contains the following

Public void exportObject (Target var1) throws RemoteException {synchronized (this) {/ / Open a port this.listen () for remote methods; + + this.exportCount;} boolean var2 = false;boolean var12 = false;try {var12 = true;super.exportObject (var1); var2 = true;var12 = false;} finally {if (var12) {if (! var2) {synchronized (this) {this.decrementExportCount ();}

Follow up the this.listen () method here

Private void listen () throws RemoteException {assert Thread.holdsLock (this); / get the TCPEndpoint object TCPEndpoint var1 = this.getEndpoint (); / / get the port number from the TCPEndpoint object, which by default is 0int var2 = var1.getPort (); if (this.server = = null) {if (tcpLog.isLoggable (Log.BRIEF)) {tcpLog.log (Log.BRIEF, "(port" + var2 + ") create server socket") } try {/ / A port number this.server = var1.newServerSocket (); Thread var3 = (Thread) AccessController.doPrivileged (new NewThreadAction (new TCPTransport.AcceptLoop (this.server), "TCP Accept-" + var2, true)); var3.start ();} catch (BindException var4) {throw new ExportException ("Port already in use:" + var2, var4);} catch (IOException var5) {throw new ExportException ("Listen failed on port:" + var2, var5) }} else {SecurityManager var6 = System.getSecurityManager (); if (var6! = null) {var6.checkListen (var2);}

From the above analysis, we know that each time a remote method object is created, the program creates a separate thread for it and assigns a port number to it.

After analyzing the process of providing object instantiation by remote methods, simply follow the getRegistry () and bind () methods

First of all, the getRegistry () code is as follows

Public static Registry getRegistry (String host, int port,RMIClientSocketFactory csf) throws RemoteException {Registry registry = null;if (port)

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Network Security

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report