Ysoserial CommonsColletions2 两个问题

本文首发于先知安全技术社区 https://xianzhi.aliyun.com/forum/topic/1756/0x00 背景

前段时间推荐一学弟好好看看Ysoserial,中间他问了我两个问题:

1)queue为什么要先用两个1占位;

2)PriorityQueue的queue 已经使用transient关键字修饰,为什么还能从流中反序列化queue中的元素(参见CommonsCollections2的源码)

这两天有时间看了源码和序列规范,真是惭愧,误人子弟了!

在寻找答案的过程中,同时也尝试通过正向的思路去理解整个payload的构造,这个思路更加直白,感兴趣的可以看看。如果单纯想知道问题答案可以直接看0x03 问题解答

0x01 Gadget chain 分析

1)Gadget chain

2)CommonsCollections2的getObject

3)待序列化反序列化的类

既然是正向思维,自然是从反序列化的本质出发。因此,很自然第一个问题是待序列化反序列化的类是哪一个。

//java.util.PriorityQueue

4)待序列化反序列化的类它的readObject方法做了什么

正如其名,PriorityQueue是优先级队列,既然是一个有优先级的队列,必然存在区分优先级的机制--排序。

在4)中,从heapify-->siftDown-->siftDownUsingComparator

在siftDown中,如果成员comparator不为空,则调用siftDownUsingComparator(名字很直白)。那么comparator(比较器)从哪里来呢?看看PriorityQueue其中一个构造方法:

比较器可以在实例化时指定。

5)CommonsCollections2使用了什么比较器

回顾2),使用了TransformingComparator

//org.apache.commons.collections4.

comparators.TransformingComparator

siftDownUsingComparator方法调用了比较器的compare方法:

this.transformer是Transformer类型(它transform方法被调用,嗅到CommonsCollection1中熟悉的味道)。

6)Transformer具体实现类是哪一个

回顾2),使用了InvokerTransformer,当然还是熟悉的InvokerTransformer!

类比,通过将和串起来完全够用了。即:承载执行命令的;承载;对队列元素排序调用的方法触发。

不知道作者 为什么要复杂化。当然,一方面可能存在某些局限我没有发现;另一方面,更复杂的链的确需要更深的功底,不得不佩服。

(下面还是顺着复杂的继续看下去)

7)PriorityQueue队列中放置了什么元素

一开始放置了两个“1”占位,后面通过反射将其中之一换为templates(这里引出第一个问题)。跟进templates生成过程:

使用javassist修改字节码,javassist是一个使用广泛的

修改字节码的库,另外还有两个常用的库是asm和cglib。

上面代码做了几件事:

整理一下,最重要的命令执行字节码已经插入了,待序列化和反序列化的类已经准备...一切就绪,看看流程是怎么串起来。

8)回头看5),InvokerTransformer的transform方法将会被调用

再接着看defineTransletClasses

12)对象实例化,触发命令执行

获取到对象的字节码之后,就可以实例化对象了:

0x02 流程概括

PriorityQueue承载TemplatesImpl,TemplatesImpl的_bytecodes装载StubTransletPayload字节码,通过javassist修改StubTransletPayload字节码插入命令执行,PriorityQueue的排序使用比较器TransformingComparator,比较器触发InvokerTransformer的transform,transform最终触发StubTransletPayload实例化,进而造成命令执行。

0x03 问题解答

1)queue为什么要先用两个1占位?

实话说,其实我也不知道。但是我最初的说法(比较器要求元素类型一致,payload这么构造是为了防止序列化过程出现异常)肯定不严谨。

简单分析

a.泛型

泛型会不会限制?

finalPriorityQueuequeue=newPriorityQueue(2,newTransformingComparator(transformer));

PriorityQueue指定Object,1会被装箱成Integer,和templates都是Object的子类,因此这里编译不会有问题。

b.比较

i. 如果放进PriorityQueue的元素不一致,会不会在比较时出现问题呢?

ii. 看看this.decorated

iii. ComparatorUtils.NATURAL_COMPARATOR 是何物

iv. 再回头看看i中value1和value2是什么

finalInvokerTransformertransformer=newInvokerTransformer("toString",newClass[],newObject[]);

因为InvokerTransformer在初始化时已经指定toString,所以调用其transform方法就会得到String。既然都是String,比较当然没有问题!

事实上,将CommonsCollections2改造如下,也没有毛病:

所以作者为什么这么写?也许更加优雅吧。

2)PriorityQueue的queue 已经使用transient关键字修饰,为什么还能从流中反序列化queue中的元素?

成员使用transient关键字修饰,的确是为了序列化时不写入流中(该成员可能含有敏感信息,出于保护不写入)。这一点可以从序列化的文件中验证:

但是,序列化规范(https://docs.oracle.com/javase/8/docs/platform/serialization/spec/output.html#a861)允许待序列化的类实现writeObject方法,实现对自己成员序列化的控制。

PriorityQueue的确实现类writeObject方法,将队列中的元素写入流中:

正是因为如下,readObject才可以从输入流中读取队列元素

0x04 参考

http://drops.wooyun.org/papers/14317

https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serialTOC.html

  • 发表于:
  • 原文链接:http://kuaibao.qq.com/s/20171211G0CA7R00?refer=cp_1026

扫码关注云+社区