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 | Runtime r = Runtime.getRuntime(); |
我们可以看到InvokerTransformer调用的时候需要传入参数名、参数类型、参数值:
可以看到传入的参数类型和值都是数组类型,然后调用这个对象的方法
所以我们也这么传入:
1 | new InvokerTransformer("exec",new Class[]{},new Object[]{}).transform(r); |
第一个是方法名,肯定是我们的exec
第二个是参数类型,我们的参数类型是String,所以填入的是String.class
第三个参数就是我们的参数值,calc
所以我们是这么写的:
1 | Runtime r = Runtime.getRuntime(); |
这样便利用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 | Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); |
这里看似已经成功了
但是有3个问题:
- 仔细想想就知道我们setValue的时候需要传入那个
runtime对象,但是这里似乎是被限制了:
- Runtime没有实现
Serializable,所以我们还需要利用反射获取到runtime - readObject还需要满足两个if条件才能到
setValue:
反射获取runtime
接下来我们修改一下获得Runtime:
1 | Class clz = Runtime.class; |
此时我们再利用invokerTransformer来执行这几个:
仔细想一下InvokerTransformer的transform方法
是通过传入的Object来获取其反射,然后根据传入的方法名、参数类型、参数来执行该方法(a.b(参数))
首先利用InvokerTransformer实现getRuntime
就是利用getMethod来实现的,所以我们去查看一下getMethod的参数类型:
一个String,一个Class数组
InvokerTransformer的Object数组填的是参数,这里其实就是getRuntime,getRuntime的参数是null
那就可以写出:
1 | 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 | Method getRuntimeM = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class); |
利用ChainedTransformer
可以看到这三行总是利用前一个的值作为后一个transform方法的参数
所以我们利用ChainedTransformer进行简化
查看ChainedTransformer的方法
只需要传入Object就可以了,这个Object就是我们方法的开始,也就是Runtime.class
简化后变成:
1 | Transformer[] transformers = new Transformer[]{ |
补全并解决其他的问题
重新往下走,补全之前的TransformedMap:
发现其实还是不能够成功
这里debug一下,先看一下是不是没进到if
可以看到Map里面是key=>value
getKey之后是key
但是memberType里面却是null,导致我们进入不了if
往回看memberType是个什么东西:
是一个叫MemberTypes的Map根据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 | Transformer[] transformers = new Transformer[]{ |
终于完成:
总结
这条链子通过
1 | AnnotationInvocationHandler.readObject() -> AbstractInputCheckedMapDecorator.setValue() -> TransformedMap.checkSetValue() -> ChainedTransformer.transform(){ |
最终导致任意方法执行
与ysoserial有些不同,ysoserial的中间是利用了lazymap的
payload:
1 | public class CC1Test { |