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 { |