JNDI注入分析
字数 2786 2025-08-24 23:51:18

JNDI注入攻击原理与利用技术详解

前言

JNDI(Java Naming and Directory Interface)注入是一种常见的Java安全漏洞,攻击者通过操纵JNDI查找操作来执行恶意代码。本文将全面分析JNDI注入的攻击原理、利用方式以及不同JDK版本的防御措施。

JNDI注入基本概念

JNDI注入主要通过RMI和LDAP两种协议进行利用,分为三种主要利用方式:

  1. 远程类加载:RMI/LDAP请求VPS远程加载恶意class,不需要本地依赖
  2. 直接反序列化:RMI/LDAP请求VPS直接反序列化gadgets执行代码
  3. 本地工厂类调用:RMI/LDAP请求VPS调用本地工厂类来执行代码

注意:RMI client和server之间确实是通过序列化传输数据的,但LDAP不是,LDAP使用标准协议传输。

LDAP注入分析

入口点分析

LDAP注入的入口在LdapCtx#c_lookup方法,通过this.doSearchOnce请求LDAP server获取LdapResult

LdapResult可能包含以下属性(忽略大小写):

  • javaSerializedData
  • javaClassName

关键属性解析

JNDI定义了以下关键属性:

JAVA_ATTRIBUTES = [
    "objectClass",          // 0
    "javaSerializedData",   // 1
    "javaClassName",       // 2
    "javaFactory",         // 3
    "javaCodeBase",        // 4
    "javaReferenceAddress",// 5
    "javaClassNames",      // 6
    "javaRemoteLocation"   // 7
]

JAVA_OBJECT_CLASSES = [
    "javaContainer",       // 0
    "javaObject",          // 1
    "javaNamingReference", // 2
    "javaSerializedObject",// 3
    "javaMarshalledObject" // 4
]

三种利用分支

com.sun.jndi.ldap.Obj.class#decodeObject方法有三种处理分支:

static Object decodeObject(Attributes var0) throws NamingException {
    String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4])); // javaCodeBase
    try {
        Attribute var1;
        // 分支1: javaSerializedData
        if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
            ClassLoader var3 = helper.getURLClassLoader(var2);
            return deserializeObject((byte[])((byte[])var1.get()), var3);
        } 
        // 分支2: javaRemoteLocation
        else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
            return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), 
                                 (String)var1.get(), var2);
        } 
        // 分支3: objectClass
        else {
            var1 = var0.get(JAVA_ATTRIBUTES[0]);
            return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && 
                  !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? 
                  null : decodeReference(var0, var2);
        }
    } catch (IOException var5) {
        NamingException var4 = new NamingException();
        var4.setRootCause(var5);
        throw var4;
    }
}

分支1:反序列化利用

当属性包含javaSerializedData时进入此分支:

if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
    ClassLoader var3 = helper.getURLClassLoader(var2);
    return deserializeObject((byte[])((byte[])var1.get()), var3);
}

关键点:

  • getURLClassLoader会检查trustURLCodebase系统属性
  • trustURLCodebase默认值为false(高版本JDK限制)

利用条件:

  • LDAP server需要返回两个属性:
    • javaClassName:任意值
    • javaSerializedData:存储反序列化利用链

分支2:javaRemoteLocation

此分支实际利用价值有限,因为无法初始化classFactoryclassFactoryLocation

分支3:引用类远程加载

objectClass属性值为javaNamingReference时进入此分支:

var1 = var0.get(JAVA_ATTRIBUTES[0]); // objectClass
return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && 
      !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? 
      null : decodeReference(var0, var2);

decodeReference方法会:

  1. 获取javaFactory值生成Reference对象
  2. 通过DirectoryManager.getObjectInstance进行实例化
  3. 调用NamingManager.getObjectFactoryFromReference获取ObjectFactory对象

利用条件:

  • LDAP server需要返回以下属性:
    • javaClassName:任意值
    • javaCodeBase:远程类加载地址(如http://x.x.x.x/
    • objectClass:固定值为javaNamingReference
    • javaFactory:远程类加载的类名(如exp,需提供exp.class文件)

JDK高版本限制:com.sun.jndi.ldap.object.trustURLCodebase默认为false

本地工厂类利用

当无法使用远程类加载时,可以寻找本地存在的工厂类:

  1. 类需实现ObjectFactory接口
  2. getObjectInstance方法中有可利用逻辑

常见可利用工厂类:

  • org.apache.naming.factory.BeanFactory(Tomcat容器)
    • 可配合javax.el.ELProcessor#eval(String)使用
    • groovy.lang.GroovyShell#evaluate(String)(SpringBoot 1.2.x)

利用条件:

  1. 本地classpath中存在目标类
  2. 类有无参构造方法
  3. 有可执行代码的方法(仅接受一个String参数)

RMI注入分析

RMI与LDAP的区别

  1. RMI使用序列化传输数据,LDAP使用标准协议
  2. RMI存在"反制server端"的可能性

RMI利用方式

1. 直接反序列化

利用点:StreamRemoteCall.class#executeCall()

调用栈:

executeCall:252, StreamRemoteCall (sun.rmi.transport)
invoke:375, UnicastRef (sun.rmi.server)
lookup:119, RegistryImpl_Stub (sun.rmi.registry)
lookup:132, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)

2. 引用类远程加载

类似LDAP方式,但仅适用于低版本(<8u113)

调用栈:

com.sun.jndi.rmi.registry.RegistryContext#lookup
sun.rmi.registry.RegistryImpl_Stub#lookup
com.sun.jndi.rmi.registry.RegistryContext#decodeObject
javax.naming.spi.NamingManager#getObjectInstance
javax.naming.spi.NamingManager#getObjectFactoryFromReference

高版本限制:

  • com.sun.jndi.rmi.object.trustURLCodebase默认为false(JDK 6u132、7u122、8u113开始)

3. 动态类加载

RMI核心特性之一,但限制严格:

  1. 需要安装RMISecurityManager并配置java.security.policy
  2. java.rmi.server.useCodebaseOnly必须为false(JDK 6u45、7u21开始默认为true)

4. 本地工厂类

原理与LDAP相同,调用NamingManager#getObjectFactoryFromReference获取工厂类后触发。

版本限制总结

利用方式 LDAP限制版本 RMI限制版本
远程类加载 <8u191 <8u113
直接反序列化 无版本限制 无版本限制
本地工厂类 无版本限制 无版本限制

关键版本变更:

  • JDK 5U45、6U45、7u21、8u121:java.rmi.server.useCodebaseOnly默认为true
  • JDK 6u132、7u122、8u113:com.sun.jndi.rmi.object.trustURLCodebase默认为false
  • JDK 11.0.1、8u191、7u201、6u211:com.sun.jndi.ldap.object.trustURLCodebase默认为false

防御建议

  1. 升级JDK到最新版本
  2. 设置系统属性:
    • com.sun.jndi.ldap.object.trustURLCodebase=false
    • com.sun.jndi.rmi.object.trustURLCodebase=false
    • java.rmi.server.useCodebaseOnly=true
  3. 避免使用不可信的JNDI查找
  4. 检查并移除危险的本地工厂类

参考资源

  1. JNDI注入原理详解 - 知乎
  2. JNDI攻击系列文章 - y4er
  3. JNDI注入与LDAP协议利用 - Seebug
  4. RMI、JRMP、JNDI深入分析 - threedr3am
JNDI注入攻击原理与利用技术详解 前言 JNDI(Java Naming and Directory Interface)注入是一种常见的Java安全漏洞,攻击者通过操纵JNDI查找操作来执行恶意代码。本文将全面分析JNDI注入的攻击原理、利用方式以及不同JDK版本的防御措施。 JNDI注入基本概念 JNDI注入主要通过RMI和LDAP两种协议进行利用,分为三种主要利用方式: 远程类加载 :RMI/LDAP请求VPS远程加载恶意class,不需要本地依赖 直接反序列化 :RMI/LDAP请求VPS直接反序列化gadgets执行代码 本地工厂类调用 :RMI/LDAP请求VPS调用本地工厂类来执行代码 注意:RMI client和server之间确实是通过序列化传输数据的,但LDAP不是,LDAP使用标准协议传输。 LDAP注入分析 入口点分析 LDAP注入的入口在 LdapCtx#c_lookup 方法,通过 this.doSearchOnce 请求LDAP server获取 LdapResult 。 LdapResult 可能包含以下属性(忽略大小写): javaSerializedData javaClassName 关键属性解析 JNDI定义了以下关键属性: 三种利用分支 com.sun.jndi.ldap.Obj.class#decodeObject 方法有三种处理分支: 分支1:反序列化利用 当属性包含 javaSerializedData 时进入此分支: 关键点: getURLClassLoader 会检查 trustURLCodebase 系统属性 trustURLCodebase 默认值为false(高版本JDK限制) 利用条件: LDAP server需要返回两个属性: javaClassName :任意值 javaSerializedData :存储反序列化利用链 分支2:javaRemoteLocation 此分支实际利用价值有限,因为无法初始化 classFactory 和 classFactoryLocation 。 分支3:引用类远程加载 当 objectClass 属性值为 javaNamingReference 时进入此分支: decodeReference 方法会: 获取 javaFactory 值生成 Reference 对象 通过 DirectoryManager.getObjectInstance 进行实例化 调用 NamingManager.getObjectFactoryFromReference 获取 ObjectFactory 对象 利用条件: LDAP server需要返回以下属性: javaClassName :任意值 javaCodeBase :远程类加载地址(如 http://x.x.x.x/ ) objectClass :固定值为 javaNamingReference javaFactory :远程类加载的类名(如 exp ,需提供 exp.class 文件) JDK高版本限制: com.sun.jndi.ldap.object.trustURLCodebase 默认为false 本地工厂类利用 当无法使用远程类加载时,可以寻找本地存在的工厂类: 类需实现 ObjectFactory 接口 getObjectInstance 方法中有可利用逻辑 常见可利用工厂类: org.apache.naming.factory.BeanFactory (Tomcat容器) 可配合 javax.el.ELProcessor#eval(String) 使用 或 groovy.lang.GroovyShell#evaluate(String) (SpringBoot 1.2.x) 利用条件: 本地classpath中存在目标类 类有无参构造方法 有可执行代码的方法(仅接受一个String参数) RMI注入分析 RMI与LDAP的区别 RMI使用序列化传输数据,LDAP使用标准协议 RMI存在"反制server端"的可能性 RMI利用方式 1. 直接反序列化 利用点: StreamRemoteCall.class#executeCall() 调用栈: 2. 引用类远程加载 类似LDAP方式,但仅适用于低版本( <8u113) 调用栈: 高版本限制: com.sun.jndi.rmi.object.trustURLCodebase 默认为false(JDK 6u132、7u122、8u113开始) 3. 动态类加载 RMI核心特性之一,但限制严格: 需要安装 RMISecurityManager 并配置 java.security.policy java.rmi.server.useCodebaseOnly 必须为false(JDK 6u45、7u21开始默认为true) 4. 本地工厂类 原理与LDAP相同,调用 NamingManager#getObjectFactoryFromReference 获取工厂类后触发。 版本限制总结 | 利用方式 | LDAP限制版本 | RMI限制版本 | |---------|-------------|------------| | 远程类加载 | <8u191 | <8u113 | | 直接反序列化 | 无版本限制 | 无版本限制 | | 本地工厂类 | 无版本限制 | 无版本限制 | 关键版本变更: JDK 5U45、6U45、7u21、8u121: java.rmi.server.useCodebaseOnly 默认为true JDK 6u132、7u122、8u113: com.sun.jndi.rmi.object.trustURLCodebase 默认为false JDK 11.0.1、8u191、7u201、6u211: com.sun.jndi.ldap.object.trustURLCodebase 默认为false 防御建议 升级JDK到最新版本 设置系统属性: com.sun.jndi.ldap.object.trustURLCodebase=false com.sun.jndi.rmi.object.trustURLCodebase=false java.rmi.server.useCodebaseOnly=true 避免使用不可信的JNDI查找 检查并移除危险的本地工厂类 参考资源 JNDI注入原理详解 - 知乎 JNDI攻击系列文章 - y4er JNDI注入与LDAP协议利用 - Seebug RMI、JRMP、JNDI深入分析 - threedr3am