栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

WebLogic反序列化之CVE-2015-4852、CVE-2016-0638、CVE-2016-3510

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

WebLogic反序列化之CVE-2015-4852、CVE-2016-0638、CVE-2016-3510

WebLogic反序列化之CVE-2015-4852、CVE-2016-0638、CVE-2016-3510

CVE-2015-4852、CVE-2016-0638、CVE-2016-3510这3个CVE常常被拿到一起来讲,因为后2个CVE都是以CVE-2015-4852利用链为基础建立的。

按照我对反序列化利用链的理解,包含2个部分:

  1. 承担恶意代码的荷载Class
  2. 启动荷载Class的启动Class或者启动机制

CVE-2015-4852的荷载Class就是较为熟知的Apache CC利用链,关于CC利用链的分析文章很多,就不在这里写了。放一篇我关于CC1的文章ysoserial gadget Commons-Collections1保姆级分析,核心就是ChainedTransformer、ConstantTransformer、InvokerTransformer。

关于承担恶意代码的荷载Class就到此为止,而怎么启动荷载,就需要先了解WebLogic的T3协议。因为WebLogic通过读取数据包里T3协议格式的序列化数据然后将其反序列化得到Object。

T3协议是WebLogic RMI使用的协议,是JRMP的强化版。

T3协议

WebLogic RMI就是WebLogic对Java RMI的实现,在功能和实现方式上稍有不同。我们来细数一下WebLogic RMI和Java RMI的不同之处。

  • WebLogic RMI支持集群部署和负载均衡。

因为WebLogic本身就是为分布式系统设计的,因此WebLogic RMI支持集群部署和负载均衡也不难理解了。

  • WebLogic RMI的服务端会使用字节码生成(Hot Code Generation)功能生成代理对象。

WebLogic的字节码生成功能会自动生成服务端的字节码到内存。不再生成Skeleton骨架对象,也不需要使用UnicastRemoteObject对象。

  • WebLogic RMI客户端使用动态代理。

在WebLogic RMI客户端中,字节码生成功能会自动为客户端生成代理对象,因此Stub也不再需要。

  • WebLogic RMI主要使用T3协议(还有基于CORBA的IIOP协议)进行客户端到服务端的数据传输。

T3传输协议是WebLogic的自有协议,它有如下特点:

  1. 服务端可以持续追踪监控客户端是否存活(心跳机制),通常心跳的间隔为60秒,服务端在超过240秒未收到心跳即判定与客户端的连接丢失。
  2. 通过建立一次连接可以将全部数据包传输完成,优化了数据包大小和网络消耗。

关于T3协议我本来想找一下有没有详细的定义,但是百度Google都没有找到,直到看到SeeBug里有篇文章说因为WebLogic闭源的原因就没有T3协议规则这方面的资料。不过网上对应Poc利用部分的T3协议分析倒是有。

具体分析参考漏洞原理和weblogic t3 协议利用与防御

  • 发现每个数据包里不止包含一个序列化魔术头。(0xac 0xed 0x00 0x05)
  • 每个序列化数据包前面都有相同的二进制串。(0xfe 0x01 0x00 0x00)
  • 每个数据包上面都包含了一个T3协议头。
  • 仔细看协议头部分,发现数据包的前4个字节正好对应着数据包长度。
  • 以及我们也能发现包长度后面的01代表请求,02代表返回。

这些点说明了T3协议由协议头包裹,且数据包中包含多个序列化的对象。

现在知道了T3协议的格式,那么接下来有2种思路

  1. 替换数据包中多个序列化对象任意一个为恶意序列化数据。

  2. weblogic发送的JAVA序列化数据的第一部分与恶意序列化数据进行拼接。

其实在我看来是一个意思,无非是选择替换第几个正常序列化部分。

网上的payload都是直接替换第1个序列化对象,我参考的Poc是这个Weblogic_direct_T3_Rces。

搞清楚怎么在T3协议数据流中加入恶意序列化数据后,接下来就可以说一说服务端是如何反序列化恶意数据并启动CC链。

从数据流到Object

Weblogic通过7001端口,获取到流量中T3协议的java反序列化数据。

通过调用栈,可以发现Weblogic最后通过ObjectInputStream读入序列化数据,并在readClassDesc(boolean unshared)方法中拿到类描述符来确定字节流中传递数据的类型,并交给对应的方法进行处理。

//ObjectInputStream.java

private ObjectStreamClass readClassDesc(boolean unshared)
    throws IOException
{
    byte tc = bin.peekByte();
    ObjectStreamClass descriptor;
    switch (tc) {
        case TC_NULL:
            descriptor = (ObjectStreamClass) readNull();
            break;
        case TC_REFERENCE:
            descriptor = (ObjectStreamClass) readHandle(unshared);
            break;
        case TC_PROXYCLASSDESC:
            descriptor = readProxyDesc(unshared);
            break;
        case TC_CLASSDESC:
            descriptor = readNonProxyDesc(unshared); //进这里
            break;
        default:
            throw new StreamCorruptedException(
                String.format("invalid type code: %02X", tc));
    }
    if (descriptor != null) {
        validateDescriptor(descriptor);
    }
    return descriptor;
}

这里会走case TC_CLASSDES,在readNonProxyDesc()里通过readClassDescriptor()拿到描述符并作为参数传给resolveClass(readDesc)方法拿到相对应的Class对象,最后通过desc.initNonProxy()初始化。

//ObjectInputStream.java

private ObjectStreamClass readNonProxyDesc(boolean unshared)
    throws IOException
{

    ObjectStreamClass desc = new ObjectStreamClass();

    ObjectStreamClass readDesc = null;
    readDesc = readClassDescriptor();

    Class cl = null;

    try {
        if ((cl = resolveClass(readDesc)) == null) {
            resolveEx = new ClassNotFoundException("null class");
        } else if (checksRequired) {}
    } catch (ClassNotFoundException ex) {}

    desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));

    return desc;
}

//InboundMsgAbbrev$ServerChannelInputStream.java
protected Class resolveClass(ObjectStreamClass var1) throws ClassNotFoundException, IOException {
    
    Class var2 = super.resolveClass(var1);
    
    return var2;
    
}

resolveClass()方法通过super.resolveClass(var1)拿到对应Class并返回。到此如何获得Class就分析结束了,接下来从ObjectInputStream.readClassDesc(boolean unshared)往上分析看看是如何通过Class得到Object的?

根据调用栈往上一层的是ObjectInputStream.readOrdinaryObject(boolean unshared)

//ObjectInputStream.java

private Object readOrdinaryObject(boolean unshared)
    throws IOException
{

    ObjectStreamClass desc = readClassDesc(false);

    Object obj;
    try {
        obj = desc.isInstantiable() ? desc.newInstance() : null;
    } catch (Exception ex) {}

    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);
    }

    return obj;
}

在readOrdinaryObject()中得到readClassDesc(false)返回的Class后desc.newInstance()将其实例化,

并且随后会调用Class对象相应的readObject()、readExternal、readResolve()方法。

这一部分参考从Weblogic原理上探究CVE-2015-4852、CVE-2016-0638、CVE-2016-3510究竟怎么一回事,非常精彩的一篇文章。

CVE-2015-4852

CVE-2015-4852利用的就是CC链,CC链的启动方法在上面也说了是readObject()方法,而如何触发readObject()方法也同样在上一部分的结尾说明了就是ObjectInputStream.readOrdinaryObject(boolean unshared),关注如下分支

//ObjectInputStream.java
private Object readOrdinaryObject(boolean unshared)
    throws IOException
{
    if (desc.isExternalizable()) {
        readExternalData((Externalizable) obj, desc);
    } else {
        readSerialData(obj, desc);
    }
}

会判断一下是否实现Externalizable接口,AnnotationInvocationHandler只实现了Serializable接口所以进入readSerialData(obj, desc),并关注invokeReadObject()方法。

//ObjectInputStream.java

private void readSerialData(Object obj, ObjectStreamClass desc)
    throws IOException
{
    slotDesc.invokeReadObject(obj, this);
}

进入invokeReadObject()方法,通过注释就知道是调用该Class对象的readObject()方法,在这里Class就是sun.reflect.annotation.AnnotationInvocationHandler。

void invokeReadObject(Object obj, ObjectInputStream in)
    throws ClassNotFoundException, IOException,
           UnsupportedOperationException
           {
               
           }

触发利用链,CVE-2015-4852结束。

CVE-2016-0638

正如最开始所说CVE-2016-0638建立在CVE-2015-4852的基础之上而来。为了防御CVE-2015-4852 WebLogic采用了黑名单把如下ban掉的方式。

weblogic.rjvm.InboundMsgAbbrev.class$ServerChannelInputStream
weblogic.rjvm.MsgAbbrevInputStream.class
weblogic.iiop.Utils.class

但是CVE-2016-0638找到了黑名单之外的weblogic.jms.common.StreamMessageImpl实现了Externalizable接口,所以在如下分支中会进入readExternalData()。

//ObjectInputStream.java
private Object readOrdinaryObject(boolean unshared)
    throws IOException
{
    if (desc.isExternalizable()) {
        readExternalData((Externalizable) obj, desc);
    } else {
        readSerialData(obj, desc);
    }
}

这回不需要看注释了,非常直接。

//ObjectInputStream.java
private void readExternalData(Externalizable obj, ObjectStreamClass desc)
    throws IOException
{
    obj.readExternal(this);
}

而StreamMessageImpl因为实现了Externalizable接口,所以需要实现readExternal()方法。

//StreamMessageImpl.java
public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException {
    super.readExternal(var1);
    byte var2 = var1.readByte();
    if (var3 >= 1 && var3 <= 3) {
        switch (var3) {
            case 1:
                this.length = var1.readInt();
                this.buffer = new byte[this.length];
                var1.readFully(this.buffer);
                ByteArrayInputStream var4 = new ByteArrayInputStream(this.buffer);
                ObjectInputStream var5 = new ObjectInputStream(var4);
                try {
                    while (true) {
                        this.writeObject(var5.readObject());
                    }
                }
                break;
        }
    }
}

在readExternal()方法里会调用var5.readObject(),而var5就是AnnotationInvocationHandler,再次RCE成功。

CVE-2016-3510

CVE-2016-3510的思路跟上一个一模一样。这回找到的是在黑名单之外的weblogic.corba.utils.MarshalledObject。而MarshalledObject利用的则是readResolve()方法,首先看看MarshalledObject的构造器。

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

    }
}

可以看到构造器会将var1写入ByteArrayOutputStream然后toByteArray()转换为Byte数组并赋值给this.objBytes,接下来分析一下MarshalledObject的。

//MarshalledObject.java
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;
    }
}

可以看到在readResolve()方法中,会把this.objBytes反序列化并调用其readObject()方法。那么就很明了了,我们只要把CC利用链作为var1把MarshalledObject new出来然后调用readResolve()就好了。

重新看回readOrdinaryObject()。·hasReadResolveMethod()方法判断desc的readResolveMethod是否为null,如果为null则不进入if语句块。

//ObjectInputStream.java

private Object readOrdinaryObject(boolean unshared)
    throws IOException
{

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

    return obj;
}

这回关注的是invokeReadResolve(obj)方法,跟进去看注释就好了。能进入就说明desc的readResolveMethod不为空,而且可以看到在invokeReadResolve(obj)方法中会通过readResolveMethod.invoke()来调用readResolve()方法。

//ObjectInputStream.java

Object invokeReadResolve(Object obj)
    throws IOException, UnsupportedOperationException
{
    ObjectStreamClass desc = readClassDesc(false);
    
    requireInitialized();
    if (readResolveMethod != null) {
        try {
            return readResolveMethod.invoke(obj, (Object[]) null);
        }
    }
}

那么readResolveMethod属性为什么就是readResolve()方法呢?把目光放到java.io.ObjectStreamClass的构造器上

public class ObjectStreamClass implements Serializable {
    
    
    private Method readResolveMethod;
    
    
    private ObjectStreamClass(final Class cl) {
        this.cl = cl;
        //...
        readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
    }
    
}

也就是说在构造ObjectStreamClass时会把cl的readResolve()方法赋值到readResolveMethod属性上。而cl自然就是MarshalledObject。

到此3个CVE都分析完了,可以说都不是很难。

参考文章:

  • WebLogic 安全研究报告
  • weblogic t3 协议利用与防御
  • 从Weblogic原理上探究CVE-2015-4852、CVE-2016-0638、CVE-2016-3510究竟怎么一回事
暑期编程PK赛 得CSDN机械键盘等精美礼品!
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1016917.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号