本文首发于先知安全技术社区 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
本文来自企鹅号 - 全球大搜罗媒体
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文来自企鹅号 - 全球大搜罗媒体
如有侵权,请联系 cloudcommunity@tencent.com 删除。