前言
之前在Java安全学习笔记(2)中写过Common-Collections这条链学习笔记,笔记是跟着p牛的java安全漫谈系列写的,利用的点都是一致的,只是触发的点,p牛用的是TransformedMap这个类,ysoserial是用的LazyMap,使用LazyMap触发的这条链用到了对象代理技术,接下来这边文章,就来分析ysoserial中Common-Collections1这条链的利用方式。
环境
commons-collections <= 3.2.1
JDK <= 8u70 (从8u71之后修复了)
利用链分析
在ysoserial中Common-Collections1的payload
![]() |
|---|
看起来比URLDNS的payload多了不少代码,拆分后来看其实也还好,首先看看构造利用点的三个关键类ConstantTransformer、InvokerTransformer、ChainedTransformer。
首先来看看ChainedTransformer这个类的transform方法
![]() |
|---|
这个方法类似于链式调用一样,将iTransformers这个数组中的对象的transform都调用一遍,并且传入的参数是上一次个对象调用后的结果,这可能听起来有点绕,用idea多跟几遍也就明白了。
再来看看ConstantTransformer这个类的transform方法
![]() |
|---|
这个方法只返回了一个对象,在创建对象的时候可以控制iConstan参数,比如创建对象我传入一个Runtime.class,那么调用该方法,返回的就是Runtime.class。
最后再来看看触发命令执行的类InvokerTransformer的transform方法
![]() |
|---|
学了反射的都知道,这个方法的作用就是传入一个对象,通过反射去获取这个这个对象的任意方法,然后再调用。并且在创建InvokerTransformer对象的时候,可以控制传入的方法名和参数。
接下来,通过new一个InvokerTransformer对象,执行transform方法,来弹出一个计算器。
![]() |
|---|
成功达到执行命令的效果。
看看ysoserial中执行命令的方式
![]() |
|---|
为什么ysoserial中不是直接调用exec来触发命令的呢?
因为Runtime这个类是没有实现Serializable接口,不能对其进行反序列化,直接反序列化会进行报错。
解决这个问题的方式就是通过反射调用Runtime.getRuntime().exec()方法。
当执行InvokerTransformer的transform方法时,我们可以先获取到Runtime的getMethod方法,在通过获取的到getMethod的去获取getRuntime方法,有点套娃的感觉,反射获取反射方法。然后再调用获取到Runtime对象,最后exec执行命令。
接下来,只需要调用ChainedTransformer的transform方法,就会将整个transforms数组中的对象串联起来,挨个执行transform方法,前一个对象的返回结果作为后一个对象的参数值。
![]() |
|---|
到这里,我们只需要找出一条xx.readObject -> gadget-> ChainedTransformer.transform即可。
ysoserial中是用的LazyMap的get方法。
![]() |
|---|
然后再通过AnnotationInvocationHandler的invoke方法调用LazyMap的get方法
![]() |
|---|
那要怎么才能调用到AnnotationInvocationHandler的invoke方法呢?答案是使用对象代理。
Java对象动态代理
Java对象代理是一种设计模式,提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。可以不修改目标对象,对目标对象功能进行拓展。生活中也有类似的例子,生活中常见的例子就是,去电影院看电影,电影院播放的电影都是制片厂商授权给电影院播放,电影院就像一个代理商,除了播放电影,在观影期间,电影院还可以再开头和结尾插播一下广告。而Java对象代理,可以通过代理原来的对象,在不修改原来对象的基础对其功能进行增强和扩展。
关于Java对象代理网上有很多文章,就不展开写了,主要还是分析利用链。
Java中对象动态代理的方法
1 | Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) |
ClassLoader loader 传递一个类加载器,使用对象默认的加载器即可
Class<?>[] interfaces 传递一个接口,对那个接口使用动态代理
InvocationHandler h 传递一个InvocationHandler对象,这个对象的作用就是处理被代理的对象需要做的逻辑。InvocationHandler 是一个接口,有一个invoke方法,当被代理对象调用任意方法时,都会调用invoke方法。
ysoserial中则是对LazyMap对象进行了动态代理,对其进行序列化,在执行readObject反序列化过程中,只要被代理过的对象调用任意方法,都会执行invoke方法。
重新再梳理一遍,我们通过构造三个InvokerTransformer这个类对象,达到了以反射的方式调用了Runtime类的exec方法执行命令,再构造一个Transformer数组,将该数组作为参数创建ChainedTransformer对象,然后只需要调用ChainedTransformer的transform方法,就能弹出计算机。
但是如果通过反序列化来调用的话,需要构造一条gadget。在LazyMap的get方法中,factory变量调用了transform方法。在AnnotationInvocationHandler的invoke方法中,memberValues变量调用了get方法。那问题来了,要怎么在反序列化的情况下调用到invoke方法呢?
先看看什么情况下,会调用到invoke?
对对象进行动态代理,创建一个继承InvocationHandler类的实例,重写invoke方法,在创建代理的时候传入这个实例,当这个实例调用任意方法的时候,就会自动触发调用invoke。
而在AnnotationInvocationHandler的readObject方法中,有一条语句memberValues.entrySet。
![]() |
|---|
假设我们传入被动态代理过后的LazyMap对象,是不是就可以触发调用invoke方法了呢?
答案是肯定的。
![]() |
|---|
![]() |
|---|
直接弹出来计算机。
利用链
至此,我们得到一个利用链
1 | AnnotationInvocationHandler->readObject |
1 | package org.example; |
参考文章
java动态代理











