java反序列化-cc1
我是看这个视频来进行学习的 cc1
这里的话不要跳过视频里的那个安装调试包的代码,跳过的话就会复现不了,得一步一步跟着做
这个cc1链的危险方法就是发现了Transformer接口
然后利用里面的这个transform函数
这里话就是本篇文章主要讲的地方,就是利用这个函数,来进行操作
这里面还写一个反射调用,并且里面的值还可控,这就是造成任意方法调用了,所以这就是这个链子的入口
这里的话,我们可以尝试利用这个函数来弹个计算器
这里是InvokerTransformer
的构造函数方法,结合上面的图片,可知通过这个构造函数传参从而弹个计算器
这就成功弹出了一个计算器
发现这里可以任意函数调用后,那么我们就得去找谁的里面调用transformer
查找后发现有21个调用,那么我们就得从里面找到一个合适的方法
(得找一个不同名的函数里面时调用transformer的)
然后我们就找到了这个方法是调用transformer的(是在TransformedMap类里边的)
然后我们就跟进看一下valueTransformer
是什么东西
是有一个保护的函数方法来给他赋值,那我们继续跟进,看一下咋调用这个函数方法
然后就发现这里有个decorate方法调用了这个函数方法,并且还可以传值
因为这个decorate里边需要传一个map类,所以我们就新建一个map类来传参。
这里只要写是因为checkvalue里只用到了valuetransformer,所以keytransfomer就为null.
但是这里虽然是能调用到了transfomer方法,但是checkvalue里的value不可控,所以我们就继续去找谁里面调用了checkvalue方法
在这里发现了一个抽象类里边的setvalue是调用这个方法的,并且我们还发现了这个抽象类是transformedMap的父类
那么我们就接着去找谁调用了setvalue方法
这里话是利用MapEntry遍历来调用setvalue方法的
这样写就能成功调用setvalue了
首先,这里给hashmap.put(“key”,”value”)的原因是简单创建一个key值,为了下方的遍历提供内容,不写的话就不会进行for循环里的遍历
这里是先for循环进行遍历,然后调用setvalue(这个时候的value值已经传进去了)里面的checkvalue,然后checkvalue就会根据传的valuetransformer进行调用transfomer.
然后就会弹计算器了
接下来我们继续找谁调用了setvalue方法
这里最好的想法是直接找谁的readobject里面调用了setvalue方法,如果没找到的话就和上面一样的方法 ,看谁里面调用了setvalue方法,最后的归宿都是找readobject里面调用了xxx方法
在这个类里边的方法找到了调用setvalue的方法
并且这个memberValues可控,那么我们就可以限定调用哪个类的setvalue方法了
这里有一个小问题就是这个类不是public类型,是一个default类型
那么不能直接调用了
必须得在这个包里才能调用了,所以这里我们就是用反射的方法进行调用了
反射的方法就写好了,这里为什么能传map,是因为这个setvalue所在的类是transformedMap的父类,然后传的map是TransformerMap类创建的,所以在反序列化的时候就会调用到那个抽象类里边的setvalue方法,这里写的Override的原因是因为下图的这个构造函数里边要求的注释class
这里有个不好的点就是value没法控制,所以得想办法来传value
还有就是这里的Runtime是没法进行序列化的(因为没有继承serializeble)
还有就是这里的两个if也要判断绕过
所以上面一共有三个问题要进行解决
先解决runtime序列化问题
这里的话先写出反射的方法,然后在写到InvokerTransformer
上
因为class能进行序列化,而Runtime不行,所以就使用反射来写
接下来我们把这段代码与InvokerTransformer结合来写
这也是个反射方法(写在函数内部的)
1 | Method method = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class); |
这段代码写的有点绕,就是利用InvokerTransformer类里边transform的反射在利用一遍,就是利用反射获取到Runtime.class的getMethod方法,然后第二位置就是传参数,第三位置就是传invoke需要的东西,就是有点嵌套的感觉,慢慢看就能把这个代码看懂了
就是根据,没用InvokerTransformer之前写的runtime反射代码来写,一步一步用InvokerTransformer来写反射来进行替换
接下来就到替换invoke了
1 | Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(method); |
这个和上面的第一次解释一样,也是和嵌套一样
上面是到Runtime.getRuntime()了,那么我们接下来直接写”exec”直接反射调用弹计算器了
1 | new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r); |
这句代码就没那么抽象了,不像前两个那么抽象
这就是替换掉之前写的没加InvokerTransformer的反射调用了,然后就可以弹计算器了
这里还暴露了一个弊端就是老是嵌套用法,前一个调用后一个,老是调用tranformer里面的反射,所以这里就利用ChainedTransformer
来解决这个问题
就是图片上两个函数,就是靠这两个来进行替换掉
然后就写好了这里就只有Runtime.class是我们可控的,其他只是起个变量名的作用而已
解决完Runtime不能反序列化后,这里就得解决那两个if判断的问题了
绕过if两个判断
这里的memberValue是传进来的map,在外面已经定义好了
并且已经有key值了,但是因为这里的key找的是map里的,但是找这个key值却是memberType的,就Override这个东西,这个东西是没有value()值的,所以我们就换一个注释函数
这个是有value()值的,名字也为value,所以我们就想着把hashMap.put("key","aaa");
改为hashMap.put("value","aaa");
那么就能绕过判断了
那么接下来就剩value的值怎么进行控制了
控制setvalue里的value值
那么我们这里就可以利用这个ConstantTransformer
类来进行解决这个问题
就是利用这一点,不管transform传啥值,最后返回的都是固定的东西,那么我们只需要控制这个固定的东西就行
新增加这一行就解决了这个value不能控制的问题了,就是在setvalue调用到checkvalue那边的时候,然后就会调用chainedTransformer.transformer
方法,然后因为在数组中因为有ConstantTransformer(Runtime.class)
存在就会调用ConstantTransformer.transformer
,然后就会返回一个Runtime.class,那么这个value就成功可控了,接下来就是循环嵌套了,名字影响不大
这里不管chainedTransformer.transformer
传进来什么value,ConstantTransformer.transformer
都会固定返回Runtime.class
然后就全部跟完了
cc1
1 | package org.example; |
上面写的非常详细了,有疑问的时候可以回来温习一下
接下来还有个lazyMap版本
我们在上面写的是TransformedMap
版本,因为在看谁调用了transformer的时候,发现了还有lazyMap这个类,然后又巧妙的发现了这个类可以用,于是就有了这个版本
lazyMap的get()方法,那么我们就和上面的思路一样,找谁调用了get()方法
然后在这个类里边发现了get方法,并且这个memberValue可控
是在这个invoke方法里面
所以这里得用动态代理方法
思路就是AnnotationInvocationHandler.readObject
里面传一个动态代理方法,然后动态代理就会使用invoke,invoke里面就会调用lazyMap.get方法(因为memberValues可控),然后get方法里面就传一个chainedTransformer
(get里面调用了transfom方法),然后就能弹计算器了
写好了,进行对这段代码进行解释一下
这里的LazyMap的decorate方法和TransformedMap.decorate方法是一个意思,这里就不多说了,下面的实例化对象使用InvocationHandler是因为readObject所在的AnnotationInvocationHandler是继承于InvocationHandler的(还有一点是不能使用这个AnnotationInvocationHandler 可以自己去idea里面试一下)
这里传的lazyMap就是可控的memberValues,在反序列化的时候这个memberValues就会调用这个entrySet()方法,因为这个方法是无参的,能符合接下来动态代理调用的invoke方法里调用get的条件,下面那行写的xProxy.newProxyInstance(h.getClass().getClassLoader(), h.getClass().getInterfaces(),h);
方法就是为了调用invoke方法,从而调用到lazymap里的get方法,然后get的方法就会调用chainedTransformer.tranfome方法,从而实现命令执行。
最后再补充一点就完成了
因为我们是想在readObject里面执行,然后刚好readobject所在的这个类是接收map类的,然后动态代理的话是必须得跟一个接口的,所以就Map map = (Map) Proxy.newProxyInstance(h.getClass().getClassLoader(), h.getClass().getInterfaces(),h);
这样来写,然后在实例化一下,把东西传进去,然后进行序列化
写好的链子
1 | package org.example; |
最后的话cc1链就全部学完了,也收获了很多