前言
之前在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动态代理