0x00 前言
打了一个月Java反序列化的基础,开始cc链。本文先按照白日梦组长和我自己的节奏走,再补一点别人的思路。
建议在看完组长的视频后再阅读本文
参考教程:
Java反序列化CommonsCollections篇(一) CC1链手写EXP_哔哩哔哩_bilibili
Java反序列化Commons-Collections篇01-CC1链 | Drunkbaby’s Blog
0x01 准备
概述
这里给出闪烁之狐大佬的文章:
Apache Commons Collections包和简介
文章前面的介绍还是很推荐大家看看的
环境
在学习cc1链之前,先搭好环境。大体需要以下三个:
- JDK8u65
- openJDK 8u65
- Maven 3.6.3(其余版本可以先试试,不行再降版本)
然后添加依赖:
1 | <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections --> |
再把openJDK
的sun
目录放到JDK8u65
的src
目录下;
最后在项目结构的SDK
的源路径里添加JDK8u65
的src
目录即可。
明确
在学习cc1链之前,我们先明确思路和细节。
思路
学习Java反序列化链时,一般从尾部方法(可以调用危险方法)入手,一步步往上找,直至找到符合要求的头部方法:readObject()。
细节
- 方法存在于类,在寻找符合的方法时我们除了要思考方法的各项性质,也必然要思考方法所在类的各项性质。
- 要思考每个方法原来的作用,这有利于打开我们的思路
0x02 cc1
开始寻找cc1链,如非特殊说明,本文的危险方法都可以用Runtime.getRuntime.exec()
代入。
项目里自行定义序列化和反序列化方法,给出如下示例:
Transfom接口
cc1链围绕Transfom
接口以及实现该接口的类及其方法展开,所以有必要先认识一下Transfom
接口:
有哪些类实现了该接口呢,下面给出几个例子:
1 | public class InvokerTransformer implements Transformer, Serializable {} |
尾部方法
概述
尾部方法可以调用危险方法,从这个角度就很好寻找尾部方法了。
不妨先想想,调用危险方法的方式有哪几种呢?举两个例子:
- 直接调用,比如方法里面直接写了:
Runtime.getRuntime.exec(cmd)
- 采用反射,通过反射调用任意方法,这当然包括调用危险方法
第一种是静态调用,第二种是动态调用。事实上,第一种是比较不好找也不太现实的,本文的尾部方法是第二种:采用反射调用。
尾部方法:InvokerTransformer.transform()
:
请认真看看该方法签名:(类签名在上文)
使用
如果transform接受参数的类型是Runtime类,且参数符合要求,那就可以通过反射调用Runtime类的exec方法。
不能忽视的是,这里的成员参数需要可控。成员参数的赋值一般在构造方法上,看看构造方法:
现在知道了构造方法可以赋值成员参数,那就可以利用transform
方法执行危险方法了,如下:
综上,现在已经找到了可以执行危险方法的尾部方法,需要进一步寻找调用了InvokerTransformer.transform
方法的方法。
不断寻找
TransformedMap.checkSetValue()
技巧
如下操作,寻找InvokerTransformer.transform用法:(也可以ctrl+鼠标左键)
找方法时,需要找的上一层方法是不同名的方法,不然的话虽然方法内容不一样,但是还是回到了原地,还要去寻找调用了该方法名的方法——这与我们最初的目的是一致的。
思考
我们要寻找的方法,其内容一定是要直白地调用InvokerTransformer.transform
吗?
不一定,因为transform是个接口,只要我们寻找的方法满足:
有实现该接口的类的实例
实例该执行了对应方法
实例的类可控
这就给我们提供了动态调用InvokerTransformer.transform方法的机会。
结果
找到了如下方法:
1 | protected Object checkSetValue(Object value) { |
方法签名:
所在类签名:
该方法满足上面的三个条件吗,看函数体:
return valueTransformer.transform(value);
上面这一行代码已经满足前两个条件了,现在我们希望成员参数valueTransformer
可控,还是看该类的构造方法:
被protected修饰,那么该类的一个方法会调用该构造函数:TransformedMap.decorate
方法
该方法是静态方法,可以直接使用。
这里有一个我觉得有趣的问题:要不要对decorate方法寻找调用链?
其实不用,因为decorate方法可以被当作构造函数,构造函数在poc里面可以被直接使用。因为Java反序列化相当于复刻对象(没有用到构造函数),而不是新构造一个对象,所以poc里最后的对象属性会在反序列化后一模一样的复刻出来
言归正传,成员参数valueTransformer
是可控的,可以通过decorate方法传参InvokerTransformer实例,实现危险方法。
poc
写写目前的poc:
再往上寻找调用了checkSetValue方法的方法。
AbstractInputCheckedMapDecorator.setValue()
概述
用ctrl+B/左键,很快就找到了这个调用,事实上就这一个调用:
不能忘记看所在类的信息,给出类签名:这是一个内部类
利用
这里的调用很直白。我们希望成员参数parent是TransformedMap类实例,并且参数Object可控。既然要成员参数可控,我们还是需要看构造方法:
可惜的是,调用该构造方法的方法并不在该类,并且setValue方法被调用的地方有很多。似乎陷入了困局。
理解
这里我们从方法作用入手:
setValue()
的作用:在 Map 中对一组 entry(键值对)进行 setValue()
操作。
所以我们只需要对Map里的entry进行遍历,对每一个entry都采用setValue()即可。
poc如下:
这样就可以通过调用setValue()
进而调用parent.checkSetValue()
进而调用invokerTransformer.transform()
进而执行危险方法。
但是这样仍然不是自动触发(readObject触发),所以我们希望遍历操作由某个类的readObject方法执行。
头部方法
概述
找到了满足该要求的类:AnnotationInvocationHandler
类,该类是用做动态代理中间处理,因为它继承了 InvocationHandler
接口。该类签名:
该类的readObject方法要想调用setValue方法也是有两个条件的:
利用
先尝试构造poc:
糟糕,没弹出来calc,是怎么回事呢?
问题
- Runtime类不能直接序列化
- AnnotationInvocationHandler的readObject方法有条件没满足
- 参数runtime没传进去(最直接明显的问题)
解决 · 问题一
Runtime类不能直接序列化,但是Runtime.class可以,那就从反射入手,正好InvokerTransformer.transform
方法有反射的功能,我们可以直接让该方法实现从创建Runtime对象到执行危险函数的一系列功能,这里直接给出ChainedTransformer
类:
ChainedTransformer
类接受一个Transformer数组,并且递归调用每一个Transformer类的transform方法。
但是还有两个问题没解决:
AnnotationInvocationHandler的readObject方法有条件没满足
参数runtime没传进去(最直接明显的问题)
解决 · 问题二
进入readObject方法,通过调试得到下面的poc:
解决 · 问题三
虽然readObject里给的Object参数不是可控的,但是有个类完美解决了这个问题:
看看该类的transform方法:
常量iConstant在构造函数里赋值
所以poc如下:
最终POC
0x04 总结
知识太多了,我还是省略了很多知识,推荐大家多看几遍组长的视频教程,我看了三遍还觉得意犹未尽。组长的视频很值得深挖,很有内涵,强推!