从Weblogic原理上探究CVE-2015-4852、CVE-2016-0638、CVE-2016-3510究竟怎么一回事
2020-11-02 11:33:03 Author: xz.aliyun.com(查看原文) 阅读量:240 收藏

​ 目前。网上关于CVE-2015-4852漏洞的资料很多,但是针对CVE-2015-4852漏洞如何修复,修复补丁又是如何生效的却少之又少;而CVE-2016-0638、CVE-2016-3510这两个漏洞又是如何绕过CVE-2015-4852补丁的,通常只是在介绍Weblogic系列漏洞时被一句话带过。

​ CVE-2015-4852、CVE-2016-0638以及CVE-2016-3510,这三个漏洞有着极其相似的地方,其本质就是利用了Weblogic反序列化机制,而官方在修复CVE-2015-4852时,也并未对这个机制进行调整,而仅仅是在此基础上增加了一个关卡:黑名单。

​ 因此,在彻底搞清楚Weblogic反序列化漏洞的原理以及如何修复这个问题之前,很有必要弄清楚Weblogic处理流量中的java反序列化数据的流程。只有清楚了这一点,才能很好的理解如下几个问题:

  1. CVE-2015-4852是如何产生的以及后续是如何修复的?

  2. 修复CVE-2015-4852,为何要在resolveClass:108,InboundMsgAbbrev\$ServerChannelInputStream (weblogic.rjvm)处添加黑名单?

  3. CVE-2016-0638、CVE-2016-3510是如何绕过修复?二者的绕过方式有何相同与不同?

Weblogic 反序列化攻击时序

​ 为了搞清楚CVE-2015-4852、CVE-2016-0638、CVE-2016-3510中的种种疑团,我们需要首先来弄明白一些原理性的东西,我们先从Weblogic
反序列化攻击时序入手,看看Weblogic是如何从流量中将序列化字节码进行反序列化。

​ 首先贴出一张Weblogic 反序列化攻击时序图

​ 这张图是从我的好朋友廖新喜大佬博客扒下来的,也欢迎大家去读一读他的关于java漏洞的分析文章:

http://xxlegend.com/2018/06/20/%E5%85%88%E7%9F%A5%E8%AE%AE%E9%A2%98%20Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AE%9E%E6%88%98%20%E8%A7%A3%E8%AF%BB/

​ 上图为一张完整的Weblogic反序列化攻击时序图,庞大而且繁杂,不如我们将其拆分开,首先说说Weblogic如何从流量数据取出序列化数据并获取其类对象的过程。

从流量数据到Class对象

​ 首先我们来看一张图:

​ Weblogic通过7001端口,获取到流量中T3协议的java反序列化数据。从上图中readObject开始,经过流程中的一步步的加工,并最终于上图流程终点处的resolveProxyClass或resolveClass处将流量中的代理类/类类型的字节流转变为了对应的Class对象。

​ 首先我们可以发现:在ObjectInputStream (java.io)中的readClassDesc方法处,存在着分叉点,导致了序列化流量流向了两个不同的分支:其中一些流量流向了readProxyDesc并最终采用resolveProxyClass获取类对象,而另一些则流向了readNonProxyDesc并最终使用resolveClass获取类对象。

readClassDesc是什么?

​ 从上文来看,流量数据经过readClassDesc并驶入了不同的处理分支。

​ 首先来看一下readClassDesc方法的官方注释:“readClassDesc方法读入并返回(可能为null)类描述符。将passHandle设置为类描述符的已分配句柄。”

​ 如果想理解官方注释的含义,需要扩充一些java序列化的知识:

​ java序列化数据在流量传输,并不是随随便便杂乱无章的,序列化数据的格式是要遵循序列化流协议。

序列化流协议定义了字节流中传输的对象的基本结构。该协议定义了对象的每个属性:其类,其字段以及写入的数据,以及以后由类特定的方法读取的数据。

字节流中对象的表示可以用一定的语法格式来描述。对于空对象,新对象,类,数组,字符串和对流中已有对象的反向引用,都有特殊的表示形式。比如说在字节流中传递的序列化数据中,字符串有字符串类型的特定格式、对象有对象类型的特定格式、类结构有着类结构。而TC_STRING、TC_OBJECT、TC_CLASSDESC则是他们的描述符,他们标识了接下来这段字节流中的数据是什么类型格式的

​ 以TC_CLASSDESC为例,TC_CLASSDESC在流量中的值是(byte)0x72,在序列化流协议中,当这个值出现后,代表接下来的数据将开始一段Class的描述(DESC=description),即TC_CLASSDESC描述符(byte)0x72后面的字节流数据为Class类型。通过这些描述符,程序可以正确的解析流量中的序列化数据。

​ 如果对这部分感兴趣,可以参照oracle文档:

https://www.oracle.com/security-alerts/cpuoct2020traditional.html

​ readClassDesc的功能很简单:读入字节流,通过读取字节流中的描述符来确定字节流中传递数据的类型,并交给对应的方法进行处理。

​ 接下来我们看看readClassDesc的实现

private ObjectStreamClass readClassDesc(boolean unshared) 
    throws IOException 
    {
    byte tc = bin.peekByte();
    switch (tc) {
        case TC_NULL:
        return (ObjectStreamClass) readNull();

        case TC_REFERENCE:
        return (ObjectStreamClass) readHandle(unshared);

        case TC_PROXYCLASSDESC:
        return readProxyDesc(unshared);

        case TC_CLASSDESC:
        return readNonProxyDesc(unshared);

        default:
        throw new StreamCorruptedException(
            String.format("invalid type code: %02X", tc));
    }
    }

​ 从readClassDesc方法的实现可见,readClassDesc中switch语句有5个分支(TC_NULL、TC_REFERENCE、TC_PROXYCLASSDESC、TC_CLASSDESC、default)。

TC_NULL描述符表示空对象引用

TC_REFERENCE描述符表示引用已写入流的对象

TC_PROXYCLASSDESC是新的代理类描述符

TC_CLASSDESC是新的类描述符

​ 那么我们为什么在上文流程图里只画出了其中两处分支(TC_PROXYCLASSDESC、TC_CLASSDESC)呢?

​ 我们先来看看Weblogic反序列化漏洞成的原理:Weblogic反序列化漏洞是由于通过流量中传入的恶意类而未得到合理的过滤,最终被反序列化而形成。

​ 从原理上来看,是weblogic对流量中序列化后的类对象处理时出现的问题。

​ 基于这一点,我们应重点关注程序是如何从流量中获取并处理类类型数据的流程。

​ TC_PROXYCLASSDESC与TC_CLASSDESC描述符标识了流量中代理类与类这两种类型的数据,因此我们重点关注TC_PROXYCLASSDESC与TC_CLASSDESC这两处分支,这也是上文流程图里只有这两处分支的原因。

​ 当readClassDesc从字节流中读取到TC_CLASSDESC描述符,说明此处程序此时要处理的字节流为普通类,程序接下来会调用readNonProxyDesc方法对这段字节流进行解析。

​ 在readNonProxyDesc方法中,程序会从该段序列化流中获取类的序列化描述符ObjectStreamClass(类序列化描述符ObjectStreamClass,其本质是对Class类的包装,可以想象成一个字典,里面记录了类序列化时的一些信息,包括字段的描述信息和serialVersionUID 和需要序列化的字段fields,以便在反序列化时拿出来使用)。随后该类的序列化描述符被传递给resolveClass方法,resolveClass方法从该类的序列化描述符中获取对应的Class对象。

​ 当readClassDesc从字节流中读取到TC_PROXYCLASSDESC描述符时,说明此处程序此时要处理的字节流为动态代理类,程序接下来会调用readProxyDesc方法进行处理,过程与上文一致,不再复述。

​ 我们以此处传入的字节流为普通类为例,接下来看看resolveClass是如何将类的序列化描述符加工成该类的Class对象

​ 位于weblogic/rjvm/InboundMsgAbbrev.class中的resolveClass方法

protected Class resolveClass(ObjectStreamClass var1) throws ClassNotFoundException, IOException {
            Class var2 = super.resolveClass(var1);
            if (var2 == null) {
                throw new ClassNotFoundException("super.resolveClass returns null.");
            } else {
                ObjectStreamClass var3 = ObjectStreamClass.lookup(var2);
                if (var3 != null && var3.getSerialVersionUID() != var1.getSerialVersionUID()) {
                    throw new ClassNotFoundException("different serialVersionUID. local: " + var3.getSerialVersionUID() + " remote: " + var1.getSerialVersionUID());
                } else {
                    return var2;
                }
            }
        }

​ 程序通过Class var2 = super.resolveClass(var1); 从ObjectStreamClass var1中获取到对应的类对象,并赋值给var2,最终通过执行return var2,将var1序列化描述符所对应的Class对象返回

​ 我们以熟悉的CVE-2015-4852利用链为例,动态调试一下resolveClass方法

​ 可见resolveClass方法成功从序列化描述符中获取到”sun.reflect.annotation.AnnotationInvocationHandler”类对象,并将其返回

​ 到目前为止,我们已经搞明白了weblogic如何将流量中的类字节流转变为对应的Class对象。以上这部分知识,有助于我们理解Weblogic官方的修复方案。而接下来我们要谈论的是在Weblogic获得到Class对象后要做的事情,通过对这部分流程的理解,将会帮助你很轻松的理解为什么CVE-2015-4852、CVE-2016-0638、CVE-2016-3510的poc是如何奏效的。

从Class对象到代码执行

​ 在获取字节流所对应的类对象之后,程序接着尝试调用类对象中的readObject、readResolve、readExternal等方法。这一操作是通过位于ObjectInputStream
(java.io)中的readOrdinaryObject方法完成的,我们看一下readOrdinaryObject方法中相关的片段

ObjectStreamClass desc = readClassDesc(false);
......

if (desc.isExternalizable()) {
    readExternalData((Externalizable) obj, desc);
} else {
    readSerialData(obj, desc);
}

handles.finish(passHandle);

if (obj != null && 
    handles.lookupException(passHandle) == null &&
    desc.hasReadResolveMethod())
{
    Object rep = desc.invokeReadResolve(obj);

​ 在readOrdinaryObject中,程序先通过readClassDesc获取Class对象(readClassDesc上文已经详细介绍了,readClassDesc会调用readNonProxyDesc并在最终调用resolveClass返回Class对象)随后readOrdinaryObject对Class对象进行进一步处理

​ 将其整理成流程图,有助于对其更好的理解,流程图如下:

​ 在Weblogic从流量中的序列化类字节段通过readClassDesc-readNonProxyDesc-resolveClass获取到普通类序列化数据的类对象后,程序依次尝试调用类对象中的readObject、readResolve、readExternal等方法。

​ 在这里提前透露下,CVE-2015-4852、CVE-2016-0638、CVE-2016-3510这三个漏洞,所利用的恰好依次是恶意类”sun.reflect.annotation.AnnotationInvocationHandler”中的readObject、”weblogic.jms.common.StreamMessageImpl”中的readExternal、以及”weblogic.corba.utils.MarshalledObject”中的readResolve方法

​ 试想一下这个场景:在没有任何防护或防护不当的时候,攻击者通过流量中传入恶意类的序列化数据,weblogic将流量中的序列化数据还原为其对应的Class对象,并尝试执行恶意类中的readObject、readResolve、readExternal等方法。这就是CVE-2015-4852、CVE-2016-0638、CVE-2016-3510漏洞的核心。

CVE-2015-4852

​ 在分析过流程之后,这个漏洞呼之欲出。简单来说,在CVE-2015-4852漏洞爆发之前,weblogic对流量中的序列化数据没有任何的校验,长驱直入的恶意数据最终被还原出其Class对象,并被Weblogic调用了其Class对象中的readObject方法,结合CVE-2015-4852细节来说就是:

  1. 精心构造的ChainedTransformer恶意链(以下简称恶意数据)

  2. 将构造好的恶意数据包裹在AnnotationInvocationHandler类的memberValues变量中

  3. 在流量中构造并传入上述制作好的AnnotationInvocationHandler类的序列化数据

  4. Weblogic获取AnnotationInvocationHandler类的Class对象

  5. Weblogic尝试调用AnnotationInvocationHandler类的readObject方法

  6. AnnotationInvocationHandler类中readObject中存在一些有助于漏洞利用的操作(AnnotationInvocationHandler的readObject方法中对其memberValues的每一项调用了setValue方法进而调用了checkSetValue)

  7. 恶意数据存放在memberValues中伺机而动。恶意数据的原理我们就不细说了,简单来说就是,当恶意数据的checkSetValue被触发,就能造成命令执行

  8. 等到readObject方法对memberValues的每一项调用setValue方法执行时,setValue方法会进而调用并触发恶意数据的checkSetValue,造成命令执行

Weblogic的防护机制

​ CVE-2015-4852这个漏洞利用出现之后,官方对Weblogic进行了一些改造,增加了一些安全防护。至于怎么防护,说起来很简单,以普通类为例,见下图:

​ resolveClass方法的作用是从类序列化描述符获取类的Class对象,在resolveClass中增加一个检查,检查一下该类的序列化描述符中记录的类名是否在黑名单上,如果在黑名单上,直接抛出错误,不允许获取恶意的类的Class对象。这样以来,恶意类连生成Class对象的机会都没有,更何况要执行恶意类中的
readObject、readResolve、readExternal呢。

​ 我们看一下具体是怎么实现的,见下图:

​ 可见更新之后多出了一个if条件分支,通过isBlackListed校验传入的类名用来处理代理类的resolveProxyClass也是一样的方式,不再复述。

​ 从整体上来看,检查模块主要在下图红框里

​ 在修复过后,CVE-2015-4852已经不能成功利用了:CVE-2015-4852所使用到的AnnotationInvocationHandler在黑名单中,会直接报错而不能获取其Class对象,更不能执行其中的readObject。

CVE-2016-0638

​ 这个漏洞主要是找到了个黑名单之外的类"weblogic.jms.common.StreamMessageImpl"

​ 简单来说,由于黑名单的限制,CVE-2015-4852利用链没法直接使用,这个漏洞像是整了个套娃,给CVE-2015-4852装进去了。

​ 为什么使用StreamMessageImpl这个类呢?其实原理也很简单。StreamMessageImpl类中的readExternal方法可以接收序列化数据作为参数,而当StreamMessageImpl类的readExternal执行时,会反序列化传入的参数并执行该参数反序列化后对应类的readObject方法。我们动态调试一下,见下图

​ 如果我们把序列化后的CVE-2015-4852利用链序列化之后丢进readExternal呢?

​ 当我们给上图StreamMessageImpl类的readExternal中传入序列化后的CVE-2015-4852利用链,在readExternal被执行时,会将CVE-2015-4852利用链数据反序列化,并在上图864行处调用其readObject方法,也就是AnnotationInvocationHandler的readObject方法

​ 好了,AnnotationInvocationHandler的readObject方法被调用了,CVE-2015-4852复活了。

​ 但是StreamMessageImpl类的readExternal要怎么被执行呢?别忘了上文分析的Weblogic反序列化流程,在获取到StreamMessageImpl类的Class对象后,程序可不止调用其readObject方法,还会尝试调用readExternal的。

CVE-2016-3510

​ 这个漏洞与上一个几乎一致,也是做了个套娃给CVE-2015-4852利用链装进去了,从而绕过了黑名单限制。

​ 这次找到的是weblogic.corba.utils.MarshalledObject

​ 首先看一下这个类的构造方法

public MarshalledObject(Object var1) throws IOException {
    if (var1 == null) {
        this.hash = 13;
    } else {
        ByteArrayOutputStream var2 = new ByteArrayOutputStream();
        MarshalledObject.MarshalledObjectOutputStream var3 = new MarshalledObject.MarshalledObjectOutputStream(var2);
        var3.writeObject(var1);
        var3.flush();
        this.objBytes = var2.toByteArray();
        int var4 = 0;

        for(int var5 = 0; var5 < this.objBytes.length; ++var5) {
            var4 = 31 * var4 + this.objBytes[var5];
        }

        this.hash = var4;
    }
}

​ 可以发现,这个类的构造方法接收一个Object类型的参数var1,然后将传入的Object参数序列化后转换为byte数组的形式赋值给this.objBytes

​ 我们接下来看看MarshalledObject的readResolve方法

public Object readResolve() throws IOException, ClassNotFoundException, ObjectStreamException {
    if (this.objBytes == null) {
        return null;
    } else {
        ByteArrayInputStream var1 = new ByteArrayInputStream(this.objBytes);
        ObjectInputStream var2 = new ObjectInputStream(var1);
        Object var3 = var2.readObject();
        var2.close();
        return var3;
    }
}

​ 可见,MarshalledObject的readResolve方法将this.objBytes反序列化,并执行其readObject。this.objBytes可以由MarshalledObject构造方法中传入的var参数控制

​ 这样以来,将CVE-2015-4852利用链传入MarshalledObject构造方法中,将MarshalledObject序列化后,则可以将CVE-2015-4852利用链保存在其this.objBytes变量中。当weblogic将构造好的MarshalledObject反序列化时,weblogic将尝试调用MarshalledObject的readResolve方法时,CVE-2015-4852利用链被执行

​ 如果对如何构造的poc细节不是很清楚,可以参照这个链接

https://github.com/zhzhdoai/Weblogic_Vuln/blob/master/Weblogic_Vuln/src/main/java/com/weblogcVul/CVE_2016_3510.java

总结

​ Weblogic的前三个反序列化漏洞并不复杂,但是对其的研究和分析是有一定的方式的,仅仅从单一的漏洞分析入手,很容易看不懂,反而把漏洞弄得复杂化。在了解了漏洞流程之后,再回头来看,这几个漏洞的原理便呼之欲出。


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