Java反序列化Commons-Collections篇01-CC1链

0x00 前言

打了一个月Java反序列化的基础,开始cc链。本文先按照白日梦组长和我自己的节奏走,再补一点别人的思路。

建议在看完组长的视频后再阅读本文

参考教程:

Java反序列化CommonsCollections篇(一) CC1链手写EXP_哔哩哔哩_bilibili

Java反序列化Commons-Collections篇01-CC1链 | Drunkbaby’s Blog

0x01 准备

概述

这里给出闪烁之狐大佬的文章:

Apache Commons Collections包和简介

文章前面的介绍还是很推荐大家看看的

环境

在学习cc1链之前,先搭好环境。大体需要以下三个:

然后添加依赖:

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->  
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

再把openJDKsun目录放到JDK8u65src目录下;

最后在项目结构SDK的源路径里添加JDK8u65src目录即可。

明确

在学习cc1链之前,我们先明确思路和细节。

思路

学习Java反序列化链时,一般从尾部方法(可以调用危险方法)入手,一步步往上找,直至找到符合要求的头部方法:readObject()。

细节

  1. 方法存在于类,在寻找符合的方法时我们除了要思考方法的各项性质,也必然要思考方法所在类的各项性质。
  2. 要思考每个方法原来的作用,这有利于打开我们的思路

0x02 cc1

开始寻找cc1链,如非特殊说明,本文的危险方法都可以用Runtime.getRuntime.exec()代入。

项目里自行定义序列化和反序列化方法,给出如下示例:

image-20241130180717595

Transfom接口

cc1链围绕Transfom接口以及实现该接口的类及其方法展开,所以有必要先认识一下Transfom接口:

image-20241130175025444

有哪些类实现了该接口呢,下面给出几个例子:

1
2
3
public class InvokerTransformer implements Transformer, Serializable {}
public class ChainedTransformer implements Transformer, Serializable {}
public class ConstantTransformer implements Transformer, Serializable {}

尾部方法

概述

尾部方法可以调用危险方法,从这个角度就很好寻找尾部方法了。

不妨先想想,调用危险方法的方式有哪几种呢?举两个例子:

  1. 直接调用,比如方法里面直接写了:Runtime.getRuntime.exec(cmd)
  2. 采用反射,通过反射调用任意方法,这当然包括调用危险方法

第一种是静态调用,第二种是动态调用。事实上,第一种是比较不好找也不太现实的,本文的尾部方法是第二种:采用反射调用。

尾部方法:InvokerTransformer.transform()

image-20241130175538673

请认真看看该方法签名:(类签名在上文)

image-20241130175730301

使用

如果transform接受参数的类型是Runtime类,且参数符合要求,那就可以通过反射调用Runtime类的exec方法。

不能忽视的是,这里的成员参数需要可控。成员参数的赋值一般在构造方法上,看看构造方法:

image-20241130180344612

现在知道了构造方法可以赋值成员参数,那就可以利用transform方法执行危险方法了,如下:

image-20241130181117018

综上,现在已经找到了可以执行危险方法的尾部方法,需要进一步寻找调用了InvokerTransformer.transform方法的方法。

不断寻找

TransformedMap.checkSetValue()

技巧

如下操作,寻找InvokerTransformer.transform用法:(也可以ctrl+鼠标左键)

image-20241130181433911

找方法时,需要找的上一层方法是不同名的方法,不然的话虽然方法内容不一样,但是还是回到了原地,还要去寻找调用了该方法名的方法——这与我们最初的目的是一致的。

思考

我们要寻找的方法,其内容一定是要直白地调用InvokerTransformer.transform吗?

不一定,因为transform是个接口,只要我们寻找的方法满足:

  1. 有实现该接口的类的实例

  2. 实例该执行了对应方法

  3. 实例的类可控

这就给我们提供了动态调用InvokerTransformer.transform方法的机会。

结果

找到了如下方法:

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

方法签名:

image-20241130182736973

所在类签名:

image-20241130182811749

该方法满足上面的三个条件吗,看函数体:

return valueTransformer.transform(value);

上面这一行代码已经满足前两个条件了,现在我们希望成员参数valueTransformer可控,还是看该类的构造方法:

image-20241130183105337

被protected修饰,那么该类的一个方法会调用该构造函数:TransformedMap.decorate方法

image-20241130183156565

该方法是静态方法,可以直接使用。

这里有一个我觉得有趣的问题:要不要对decorate方法寻找调用链?

其实不用,因为decorate方法可以被当作构造函数,构造函数在poc里面可以被直接使用。因为Java反序列化相当于复刻对象(没有用到构造函数),而不是新构造一个对象,所以poc里最后的对象属性会在反序列化后一模一样的复刻出来

言归正传,成员参数valueTransformer是可控的,可以通过decorate方法传参InvokerTransformer实例,实现危险方法。

poc

写写目前的poc:

image-20241130185945222

再往上寻找调用了checkSetValue方法的方法。

AbstractInputCheckedMapDecorator.setValue()

概述

用ctrl+B/左键,很快就找到了这个调用,事实上就这一个调用:

image-20241130190356202

不能忘记看所在类的信息,给出类签名:这是一个内部类

image-20241130190751888

利用

这里的调用很直白。我们希望成员参数parent是TransformedMap类实例,并且参数Object可控。既然要成员参数可控,我们还是需要看构造方法:

image-20241130190854881

可惜的是,调用该构造方法的方法并不在该类,并且setValue方法被调用的地方有很多。似乎陷入了困局。

理解

这里我们从方法作用入手:

setValue() 的作用:在 Map 中对一组 entry(键值对)进行 setValue() 操作。

所以我们只需要对Map里的entry进行遍历,对每一个entry都采用setValue()即可。

poc如下:

image-20241130192013223

这样就可以通过调用setValue()进而调用parent.checkSetValue()进而调用invokerTransformer.transform()进而执行危险方法。

但是这样仍然不是自动触发(readObject触发),所以我们希望遍历操作由某个类的readObject方法执行。

头部方法

概述

找到了满足该要求的类:AnnotationInvocationHandler类,该类是用做动态代理中间处理,因为它继承了 InvocationHandler 接口。该类签名:

image-20241130192443786

该类的readObject方法要想调用setValue方法也是有两个条件的:

image-20241130192314104

利用

先尝试构造poc:

image-20241130193134523

糟糕,没弹出来calc,是怎么回事呢?

问题

  1. Runtime类不能直接序列化
  2. AnnotationInvocationHandler的readObject方法有条件没满足
  3. 参数runtime没传进去(最直接明显的问题)

解决 · 问题一

Runtime类不能直接序列化,但是Runtime.class可以,那就从反射入手,正好InvokerTransformer.transform方法有反射的功能,我们可以直接让该方法实现从创建Runtime对象到执行危险函数的一系列功能,这里直接给出ChainedTransformer类:

image-20241130194147649

ChainedTransformer类接受一个Transformer数组,并且递归调用每一个Transformer类的transform方法。

但是还有两个问题没解决:

AnnotationInvocationHandler的readObject方法有条件没满足

参数runtime没传进去(最直接明显的问题)

解决 · 问题二

进入readObject方法,通过调试得到下面的poc:

image-20241130194450504

解决 · 问题三

虽然readObject里给的Object参数不是可控的,但是有个类完美解决了这个问题:

image-20241130194620922

看看该类的transform方法:

image-20241130194651309

常量iConstant在构造函数里赋值

image-20241130194739709

所以poc如下:

image-20241130194906484

最终POC

image-20241130195002383

0x04 总结

知识太多了,我还是省略了很多知识,推荐大家多看几遍组长的视频教程,我看了三遍还觉得意犹未尽。组长的视频很值得深挖,很有内涵,强推!