CommonsCollections1
版权申明:本文为原创文章,转载请注明原文出处
CommonsCollections1
前景提要
jdk版本:8u65 下载链接:Java 存档下载 — Java SE 8 | Oracle 中国
cc版本:3.2.1
1 | <dependency> |
用https://hg.openjdk.org/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip下载到的zip文件中的src/share/classes目录下的sun添加到jdk_8u65/目录下的src.jar解压后的src文件中,再将src/目录添加到项目的sdk源路径中,可实现将class文件转换为java(方便调试)

入口
此处为 commons-collections 中 Transformer
的实现类 InvokerTransformer 中的 transformer
方法如果此处的 this.IMethodName 和
this.iParamTypes
可以自己构造,即可实现执行任意方法调用功能

利用漏洞
简单回顾一下动态调用方法,首先获取Class对象,再获取方法对象,再传入实例名和参数执行该实例的该方法

终点确认
尝试创建InvokerTransformer对象,并调用transformer方法
1 | new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(Runtime.getRuntime()); |
也就是此处我们只要能创建 InvokerTransformer 类并执行其
transformer 方法就可以实现执行任意方法
因此下一步是找可以构造指定类执行transformer方法的函数
进阶1
根据调用函数选择的是TransformerMap类的checkSetValue方法,此处的
valueTransformer 是可构造的

找到 valueTransformer 和 keyTransformer
对象传参定义的地方,为此处的TransformedMap方法

找到调用 TransformedMap
方法的地方,这里的decorate实现了传参创建类的方法

找到调用 checkSetValue
函数的地方,因为这是protect属性,所以只能是内部调用,此处的MapEntry方法定义好parent后,下面的setValue方法实现了checkSetValue的调用,并且传入对象可控制

接着使用该类实现调用InvokerTransformer的transformer方法,这里的MapEntry就相当于遍历键值对,这里的entry也就是上面传入的parent参数,因此调用entry的setValue方法就能最终实现调用对checkSetValue函数的调用,为了保证能够正常遍历执行,map里面需要存入至少一对键值对

接着寻找调用setValue函数的地方(最好是readObject方法)
找到调用setValue的readObject方法的类
通过寻找发现AnnotationInvocationHandler类的readObject方法中有调用Map.Entry的setValue方法

其中关键部分为
1 | memberValue.setValue( |
因此要做的就是更改此处的memberValues和传入的参数部分
首先更改memberValues变量,查看该变量的赋值过程,如图所示,是可控的

代码构造
首先需要清楚的是Runtime类并不是一个可序列化对象(没有implements Serializable),因此不能直接定义该对象,只能用反射
因此Runtime.getRuntime对象获取方式更改为如下
1 | Class<?> clazz = Runtime.class; |
而Method对象是不可序列化的,因此都使用上面的InvokerTransformer类实现
1 | Class<?> clazz = Runtime.class; |
不难发现,整体流程都是一样的,先建立InvokerTransformer对象,再调用其transform方法,因此以上代码进一步简化,通过使用ChainedTransformer数组,该数组调用transform方法可实现上一结果的输出作为下一结果的输入
1 | public Object transform(Object object) { |
因此我们只需要能执行transformerChain实例的transform方法,并传入参数Runtime.class作为第一个InvokerTransformer对象的传参,以上代码转换为
1 | Transformer[] transformers = new Transformer[]{ |
想办法传入恰当参数使得readObject方法可以执行该setValue方法,回到AnnotationInvocationHandler的readObject方法,我们需要确保能进入memberType!=null才能继续向下执行

找到memberType的来源

找到type的来源,是个注释类,并且要求该注释类有从memberValues键值对中获取到的键,memberValues是我们可控制的,因此需要找到有键的注释类

此处选择Target类,有键value

memberValues传入的键值对中的键应是value
1 | HashMap<Object,Object> map = new HashMap<>(); |
但是此处setValue中的对象我们难以控制,而这里setValue中的对象相当于是要传递给transformerChain的transform方法,该方法会依次执行transformerChain数组中的InvokerTransformer对象的transform方法,因此此处选择在数组中再增加一对象,使得无论传什么参都能正确返回第一个结果,只要第一个结果正确,后面肯定会依次执行,也就是此处的ConstantTransformer类

由此,整体构造代码如下,用反射的方式获取AnnotationInvocationHandler对象是因为该方法不是public对象,不能直接在外部实例化
1 | Transformer[] transformers = new Transformer[]{ |
进阶2
选择使用lazyMap类的get方法
LazyMap是一种装饰模式的实现,它包装了另一个Map对象,并添加了按需创建对象的功能。当通过get(Object key)方法访问一个不存在于Map中的键时,它会使用一个工厂对象来创建该键对应的值,并将其放入Map中。
此处的factory是可控制的,并且此处的key是不需要定义的,此处的transform方法并不会使用传入的参数key

确定能执行get方法的漏洞点
此处选择的是AnnotationInvocationHandler类的invoke方法,因为上面已经分析了memberValues(Map)对象是可控制的,而在此处调用了memberValues对象的get方法

因此我们想办法使得AnnotationInvocationHandler的readObject方法调用此处的invoke方法,而此处调用enrtySet方法刚好满足条件,只要memberValues是AnnotationInvocationHandler类的代理对象,当memberValues调用该方法时,就会自动调用AnnotationInvocationHandler的invoke方法,最终实现该链

代码构造
memberValues就变成了AnnotationInvocationHandler的代理对象,并且由于传参限制,必须是Map类
1 | Transformer[] transformers = new Transformer[]{ |
总结
下图是这两个链子的流程图,都是找调用transform方法的类
- 进阶一找的是
TransformedMap的checkValue方法,然后在AnnotationInvocationHandler类中的readObject方法中能实现调用checkValue方法,通过传入构造的TransformedMap对象,构造的TransformedMap对象会传入构造的ChainedTransformed对象,最终实现ChainedTransformed的transform方法的调用 - 进阶二找的是LazyMap的get方法,原理是
AnnotationInvocationHandler类中的invoke方法实现了调用get方法,因此想办法使得AnnotationInvocationHandler的readObject方法中能实现调用其invoke方法,因此传入对象为AnnotationInvocationHandler类的实例,实例调用函数一定会调用该实例的invoke方法来调用实例,进而实现get方法调用

CommonsCollections1
install_url to use ShareThis. Please set it in _config.yml.



