一次曲折的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注入
- 绕过方向:
- 利用反序列化
- 利用本地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 完整利用链
- 创建目录:
Serializable payload8 = XyzFileMkdir1.getObject("/opt/tomcat/aaa/bbb/http:/192.168.176.1:8888/whatever"); - 实现文件写入RCE
5. 关键知识点总结
-
反序列化探测:
- 注意服务端可能自定义反序列化流程(如先读长度)
- 客户端依赖分析可推测服务端环境
-
安全限制绕过:
- JDK安全限制可通过系统属性修改尝试绕过
- 注意类加载时机对属性设置的影响
-
JNDI注入替代方案:
- 当forceString不可用时,考虑GenericNamingResourcesFactory
- SystemConfiguration可用于系统属性修改
-
文件操作利用:
- MemoryUserDatabaseFactory可实现文件写入
- 目录创建是Linux下利用的关键步骤
-
环境适应性:
- Windows和Linux在文件路径处理上有差异
- 需要根据目标环境选择合适的技术路径
6. 防御建议
- 及时升级JDK和中间件版本
- 限制反序列化操作,使用白名单机制
- 配置严格的安全管理器策略
- 禁用不必要的JNDI查找功能
- 监控系统属性修改操作
- 限制文件系统操作权限