一次曲折的JDK反序列化到JNDI注入绕过(no forceString)
字数 2496 2025-08-19 12:40:34

JDK反序列化到JNDI注入绕过技术研究

1. 漏洞发现过程

1.1 初始发现

  • 通过nmap扫描发现Jetty服务
  • 使用dirsearch扫描到/metrics/路径
  • 通过RDP获取Windows账号,找到服务安装包并分析

1.2 代理设置与调试

  • 安装时设置代理为Burp地址
  • 将依赖jar包导入IDEA进行调试
  • 通过HTTP请求发现序列化对象传输

1.3 反序列化探测

  • 尝试使用ysoserial/Urldns.jar payload但无响应
  • 分析响应中的Exception stackTrace
  • 发现服务端反序列化流程:先读取Integer类型,再根据数值读取后续字节进行反序列化

2. 反序列化利用技术

2.1 Gadgets探测

  • 尝试Jackson + Spring-aop gadget
  • 分析客户端依赖:
    • jython:无依赖
    • Commons-Collections 2系列:已修复
    • Groovy 2.4.21:已修复
    • commons-fileupload 1.3.3:已修复
    • Commons-Beanutils:只有2系列

2.2 Commons-Beanutils2利用

  • 发现使用commons-beanutils2(GitHub 0 star项目)
  • 关键gadget存在但包名修改
  • 构造payload时遇到安全限制:
    java.lang.UnsupportedOperationException: When Java security is enabled, support for deserializing TemplatesImpl is disabled. This can be overridden by setting the jdk.xml.enableTemplatesImplDeserialization system property to true.
    

2.3 反序列化到JNDI注入

  • 分析CommonsBeanutils2 gadget链:
    java.util.PriorityQueue#readObject
    -> java.util.Comparator#compare
    -> org.apache.commons.beanutils2.BeanComparator#compare
    -> org.apache.commons.beanutils2.PropertyUtils#getProperty
    -> Xxx#getYyy() (需Serializable、利用空参数getter方法)
    
  • 传统TemplatesImpl#getOutputProperties被禁用
  • 转向oracle.jdbc.rowset.OracleCachedRowSet#getConnection实现JNDI注入

3. JNDI注入绕过技术

3.1 高版本JDK限制

  • JDK高版本默认限制JNDI注入
  • 绕过方向:
    1. 利用反序列化
    2. 利用本地javax.naming.spi.ObjectFactory中的Reference

3.2 Tomcat BeanFactory限制

  • Tomcat org.apache.naming.factory.BeanFactory的forceString功能:
    • Tomcat 7:无此功能
    • Tomcat 9.0.63+/8.5.79+:移除此功能
  • 目标环境为Tomcat 9.0.64,无法使用forceString

3.3 替代方案

  1. 反序列化getter:尝试其他getter方法
  2. JNDI注入setter:寻找setAbc(String payload)类恶意方法

3.4 使用GenericNamingResourcesFactory

  • org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory特点:
    • 支持调用类中所有以set开头的方法(包括static方法)
    • 相比BeanFactory更灵活,不依赖属性查找

3.5 SystemConfiguration利用

  • org.apache.commons.configuration2.SystemConfiguration:
    • setSystemProperties方法可设置系统属性
    • 接收fileName参数(可构造为URL)
    • 可远程加载key=value格式的属性文件

3.6 系统属性修改尝试

  1. 尝试设置jdk.xml.enableTemplatesImplDeserialization=true
    • 成功修改但依然无法利用
  2. 尝试设置JNDI相关属性:
    • com.sun.jndi.ldap.object.trustURLCodebase=true
    • com.sun.jndi.rmi.object.trustURLCodebase=true
    • 失败原因:VersionHelper.TRUST_URL_CODE_BASE在类加载时初始化,后续修改无效

4. MemoryUserDatabaseFactory利用

4.1 XXE利用

  • org.apache.catalina.users.MemoryUserDatabaseFactory:
    • 设置pathname后会转换为URL
    • 通过database#open()解析XML内容
    • 可实现blind SSRF => blind XXE
    • 限制:只能读取/etc/hostname等有限信息

4.2 文件写入RCE

  • 原理:
    • database.save()在System.getProperty("catalina.base")下创建文件
    • 文件命名规则:pathname + ".new"
    • Windows下可路径穿越,Linux需手动创建目录

4.3 目录创建挑战

  • 需要先在System.getProperty("catalina.base")下创建目录
  • 可用方法:
    • Files.createDirectory/createDirectories
    • File.mkdir/mkdirs
  • 最终找到项目特定代码通过getOutputStream创建目录

4.4 完整利用链

  1. 创建目录:
    Serializable payload8 = XyzFileMkdir1.getObject("/opt/tomcat/aaa/bbb/http:/192.168.176.1:8888/whatever");
    
  2. 实现文件写入RCE

5. 关键知识点总结

  1. 反序列化探测

    • 注意服务端可能自定义反序列化流程(如先读长度)
    • 客户端依赖分析可推测服务端环境
  2. 安全限制绕过

    • JDK安全限制可通过系统属性修改尝试绕过
    • 注意类加载时机对属性设置的影响
  3. JNDI注入替代方案

    • 当forceString不可用时,考虑GenericNamingResourcesFactory
    • SystemConfiguration可用于系统属性修改
  4. 文件操作利用

    • MemoryUserDatabaseFactory可实现文件写入
    • 目录创建是Linux下利用的关键步骤
  5. 环境适应性

    • Windows和Linux在文件路径处理上有差异
    • 需要根据目标环境选择合适的技术路径

6. 防御建议

  1. 及时升级JDK和中间件版本
  2. 限制反序列化操作,使用白名单机制
  3. 配置严格的安全管理器策略
  4. 禁用不必要的JNDI查找功能
  5. 监控系统属性修改操作
  6. 限制文件系统操作权限
JDK反序列化到JNDI注入绕过技术研究 1. 漏洞发现过程 1.1 初始发现 通过nmap扫描发现Jetty服务 使用dirsearch扫描到/metrics/路径 通过RDP获取Windows账号,找到服务安装包并分析 1.2 代理设置与调试 安装时设置代理为Burp地址 将依赖jar包导入IDEA进行调试 通过HTTP请求发现序列化对象传输 1.3 反序列化探测 尝试使用ysoserial/Urldns.jar payload但无响应 分析响应中的Exception stackTrace 发现服务端反序列化流程:先读取Integer类型,再根据数值读取后续字节进行反序列化 2. 反序列化利用技术 2.1 Gadgets探测 尝试Jackson + Spring-aop gadget 分析客户端依赖: jython:无依赖 Commons-Collections 2系列:已修复 Groovy 2.4.21:已修复 commons-fileupload 1.3.3:已修复 Commons-Beanutils:只有2系列 2.2 Commons-Beanutils2利用 发现使用commons-beanutils2(GitHub 0 star项目) 关键gadget存在但包名修改 构造payload时遇到安全限制: 2.3 反序列化到JNDI注入 分析CommonsBeanutils2 gadget链: 传统TemplatesImpl#getOutputProperties被禁用 转向oracle.jdbc.rowset.OracleCachedRowSet#getConnection实现JNDI注入 3. JNDI注入绕过技术 3.1 高版本JDK限制 JDK高版本默认限制JNDI注入 绕过方向: 利用反序列化 利用本地javax.naming.spi.ObjectFactory中的Reference 3.2 Tomcat BeanFactory限制 Tomcat org.apache.naming.factory.BeanFactory的forceString功能: Tomcat 7:无此功能 Tomcat 9.0.63+/8.5.79+:移除此功能 目标环境为Tomcat 9.0.64,无法使用forceString 3.3 替代方案 反序列化getter:尝试其他getter方法 JNDI注入setter:寻找setAbc(String payload)类恶意方法 3.4 使用GenericNamingResourcesFactory org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory特点: 支持调用类中所有以set开头的方法(包括static方法) 相比BeanFactory更灵活,不依赖属性查找 3.5 SystemConfiguration利用 org.apache.commons.configuration2.SystemConfiguration: setSystemProperties方法可设置系统属性 接收fileName参数(可构造为URL) 可远程加载key=value格式的属性文件 3.6 系统属性修改尝试 尝试设置jdk.xml.enableTemplatesImplDeserialization=true 成功修改但依然无法利用 尝试设置JNDI相关属性: com.sun.jndi.ldap.object.trustURLCodebase=true com.sun.jndi.rmi.object.trustURLCodebase=true 失败原因:VersionHelper.TRUST_ URL_ CODE_ BASE在类加载时初始化,后续修改无效 4. MemoryUserDatabaseFactory利用 4.1 XXE利用 org.apache.catalina.users.MemoryUserDatabaseFactory: 设置pathname后会转换为URL 通过database#open()解析XML内容 可实现blind SSRF => blind XXE 限制:只能读取/etc/hostname等有限信息 4.2 文件写入RCE 原理: database.save()在System.getProperty("catalina.base")下创建文件 文件命名规则:pathname + ".new" Windows下可路径穿越,Linux需手动创建目录 4.3 目录创建挑战 需要先在System.getProperty("catalina.base")下创建目录 可用方法: Files.createDirectory/createDirectories File.mkdir/mkdirs 最终找到项目特定代码通过getOutputStream创建目录 4.4 完整利用链 创建目录: 实现文件写入RCE 5. 关键知识点总结 反序列化探测 : 注意服务端可能自定义反序列化流程(如先读长度) 客户端依赖分析可推测服务端环境 安全限制绕过 : JDK安全限制可通过系统属性修改尝试绕过 注意类加载时机对属性设置的影响 JNDI注入替代方案 : 当forceString不可用时,考虑GenericNamingResourcesFactory SystemConfiguration可用于系统属性修改 文件操作利用 : MemoryUserDatabaseFactory可实现文件写入 目录创建是Linux下利用的关键步骤 环境适应性 : Windows和Linux在文件路径处理上有差异 需要根据目标环境选择合适的技术路径 6. 防御建议 及时升级JDK和中间件版本 限制反序列化操作,使用白名单机制 配置严格的安全管理器策略 禁用不必要的JNDI查找功能 监控系统属性修改操作 限制文件系统操作权限