Try   HackMD

Serialize và Deserialize trong Java

Java là một ngôn ngữ lập trình hướng đối tượng, chính vì thế khi có nhu cầu trao đổi dữ liệu dưới dạng các object thì các format thông thường như JSON hay XML sẽ không thể nào làm tốt, do đó ta cần sử dụng serialized data để thực hiện việc trao đổi dữ liệu của object dưới dạng bytes

Ví dụ code serialize và deserialize

// User.java
import java.io.Serializable;

public class User implements Serializable {
    private String name;
    private int age;

    public User() {
    }
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    ...
}
// main.java
import java.io.*;

public class Main {
    public static void main(String[] args) {
        User a = new User("endy", 20);

        // Serialize data
        try {
            FileOutputStream fileOut = new FileOutputStream("data.ser");
            ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);
            objectOut.writeObject(a);
            objectOut.close();
            fileOut.close();
            System.out.println("Object serialized and saved to data.ser");
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Deserialize data
        try {
            FileInputStream fileIn = new FileInputStream("data.ser");
            ObjectInputStream objectIn = new ObjectInputStream(fileIn);
            User deserializedObject = (User) objectIn.readObject();
            objectIn.close();
            fileIn.close();

            // Print the deserialized object
            System.out.println("Deserialized Object:");
            System.out.println("Name: " + deserializedObject.getName());
            System.out.println("Age: " + deserializedObject.getAge());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Đoạn code trên sẽ khởi tạo object của class User sau đó dùng method writeObject để serialize và ghi vào file data.ser
Tiếp theo sử dụng method readObject để deserialize và in ra console

Sau khi đoạn code trên thực thi ta sẽ nhận được kết quả:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Ta có thể thấy format serialized data trong Java khác hoàn toàn so với serialized data trong PHP thông qua file data.ser

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

<!-- Serialized data trong PHP -->

O:4:"User":2:{s:10:"\0User\0name";N;s:9:"\0User\0age";N;}

Serialized data của Java sẽ có cấu trúc như sau

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Note: Kiểu dữ liệu của thuộc tính age là I (interger), còn giá trị của thuộc tính age là 20 tương đương với 0x14

Ta có thể dùng jdeserialize-1.2.jar để hiển thị serialized data một cách dễ nhìn hơn

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Và cũng tương tự như lỗ hổng Unsafe Deserialize trong các ngôn ngữ khác thì Java Deserialized Vulnerability sẽ xảy ra khi untrusted data rơi vào quá trình serialize và deserialize, attacker có thể kiếm soát Object được deserialized từ đó dẫn đến nhiều hậu quả khác nhau, tùy theo logic code của chương trình

Reflection trong Java

Reflection là một khái niệm quan trọng trong khai thác lỗ hổng Java deser, tưởng tượng ta tìm được một chain có thể dẫn đến RCE, tuy nhiên muốn làm được ta phải thay đổi được giá trị của một property private của một class trong chain đó.

Để làm được vậy ta sẽ dùng đến Reflection đây là một API cho phép thay đổi giá trị của các thuộc tính trong quá trình Runtime, nó thường được dùng để set giá trị cho các private properties của class

Ví dụ:
Ta có class User như sau

public class User {
    public String name;
    private boolean isAdmin = false;

    public User(String name) {
        this.name = name;
    }
    ...
}

Để thay đổi giá trị của isAdmin mà không cần dùng đến setter ta sẽ dùng Reflection API

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws IllegalAccessException {
        User a = new User("endy");

        Field f = null;
        try {
            f = a.getClass().getDeclaredField("isAdmin");
            f.setAccessible(true);
            f.set(a, true);
        }
        catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }

        System.out.println(a.isAdmin());
    }
}

Kết quả:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Ngoài ra Reflection còn được sử dụng trong trường hợp ta muốn gọi method của một class nhưng không muốn tạo instant của class đó, ví dụ có một private class XYab là public method của class XY. Ta muốn sử dụng ab nhưng không muốn tạo instant của XY nên sẽ dùng Reflection
Syntax:

<Object>.class.getMethod("<method name>").invoke(agrs);

Gadgetchain trong Java

Ở trên là những gì cơ bản nhất về serialized/deserialized và Reflection trong java, phần quan trọng nhất là làm sao từ một methods deserialize như readObject mà dẫn đến RCE, writefile hay bất kỳ thứ gì mà ta muốn.

Ta sẽ sử dụng gadgetchain để làm được điều đó, có thể ví dụ các object, method như là các đoạn ống nước, ta phải nối chúng lại làm sao để nước có thể đi từ source đến sink, khiến untrusted data rơi vào method mà ta muốn thực thi. Đoạn đường ống nước nối từ source đến sink chính là gadgetchain

Nhắc đến Java Deser mà không nhắc đến ysoserial thì rất thiếu sót, ysoserial là một tool cung cấp cho ta một số gadgetchain phổ biến trong thư viện của Java

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Để sử dụng thì ta chỉ cần gọi tên gadgetchain nó sẽ tự động gen payload cho ta

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Tiếp theo mình sẽ phân tích lại một gadgetchain đơn giản là URLDNS. Cách phân tích và các bước mình tham khảo theo blog nàyblog này. Các bạn có thể xem bài viết góc để dễ hiểu hơn, ở đây mình chỉ phân tích lại để phục vụ nhu cầu của mình thôi

URLDNS

Gen payload

Gadgetchain này sẽ gửi DNS query đến URL mà ta truyền vào, thông thường gadgetchain này được sử dụng để test xem target có thể deserialize object được hay không. Đây cũng là gadgetchains đơn giản và dễ phân tích nhất trong ysoserial

Đầu tiên ta sẽ thử gen payload để sử dụng

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Khi chạy chương trình thì tại request repo của mình nhận được dns query

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Vậy thì gadgetchain này bao gồm những gì?

Tại src gen payload của ysoserial ta biết được chain này bao gồm:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Ta cũng biết được cách ysoserial tạo payload

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Đầu tiên object của class URL được tạo với url là input của ta, handler là object của class URLStreamHandler
Sau đó class u được put vào hashmap
Và cuối cùng là dùng Reflecton để thay đổi giá trị của property private hashCode

Cụ thể quá trình deser

Sau khi biết được đường đi từ source đến sink, bây giờ tiến hành debug hoii
Vì gadgetchain này lợi dụng class có sẵn trong Java nên ta không cần phải khai báo thêm thư viện gì cả

Sơ đồ gadgetchain là như sau:

HashMap.readObject()
    ->HashMap.putVal()
        ->HashMap.hash()
            ->URL.hashCode()

Gadgetchain này sẽ được trigger khi ta gọi readObject của clas Hashmap, do đó để debug ta sẽ bắt đầu từ Hashmap

Tại InteliJ nhấn Ctrl+N và tìm Hashmap để xem src của class HashMap

Tại đây nhấn Ctrl+F12 để tìm methods readObject

Note: Java cho phép dev có thể ghi đè lên các methods readObjectwriteObject, và thông thường dev cũng thích làm thế. Do đó ở một số class methods readObject hoặc writeObject sẽ khác so với mặc định của Java

Tại method readObject của Hashmap, nó sẽ loop qua từng key của hashmap và thực hiện putVal(hash(key))

Đặt breakpoint tại đây để xem giá trị của key

Ta thấy key chính là object URL mà ta truyền vào

Tiếp tục nhấn F7 và chọn method hash để nhảy vào method này

Method này gọi đến key.hashCode() trong trường hợp này key chính là object URL, nghĩa là nó đang gọi đến URL.hashCode()

Tiếp tục nhảy vào method hashCode

Method này sẽ kiểm tra giá trị của property hashCode có khác -1 hay không, nếu khác thì tiếp tục gọi đến handler.hasCode() còn nếu không thì gadgetchain của ta bị gãy từ đây.

Tuy nhiên vấn đề nằm ở hashCode được defined mặc định là -1

Vì đây là property private nên ta không thể thay đổi giá trị của nó lúc khai báo, nhìn lại code gen payload của ysoserial ta thấy nó sử dụng Reflection để thay đổi giá trị của hashCode trong quá trình runtime

Reflection trên là một class của ysoserial.payloads.util nên để tái hiện lại mình sẽ dùng cách mặc địch của Java

URL u = new URL(null, "http://ydg1nw3a.requestrepo.com", handler);
Field field = URL.class.getDeclaredField("hashCode");
field.setAccessible(true);
field.set(u, 1337);

Ta có thể test bằng cách dùng Evalute Expression của InteliJ

Khi comment dòng set ta thấy giá trị của hashCode là -1, khi gỡ comment dòng đó thì:

Giá trị của hashCode đã là 1337. Vậy thông qua reflection ta có thể thay đổi giá trị của hashCode

Gadget của ysorial đến đây là hết, tuy nhiên ta vẫn chưa thấy dòng nào thực hiện DNS query, do đó mình sẽ tiếp tục debug

Sau khi bypass được hashCode = -1, tiếp tục code gọi đến handler.hashCode

handler này là class của object URLStreamHandler

F7 để nhảy vào handler.hashCode thì ta thấy nó gọi đến getHostAddress với u là class URL ta truyền vào

method này sẽ gọi đến InetAddress.getByName() để resolve DNS đến host của ta

Vậy thì gadgetchain đầy đủ sẽ là

HashMap.readObject()
    ->HashMap.putVal()
        ->HashMap.hash()
            ->URL.hashCode()
                ->URLStreamHandler.getHostAddress()
                    ->InetAddress.getByName()

Đây là gadgetchain đơn giản nhất trong các gadgetchain ysoserial, nó giúp mình có cái nhìn tổng quát về gadgetchain trong Java cũng như là học được cách debug trong InteliJ. Phần tiếp theo mình sẽ phân tích thêm một gadgetchain nữa trong bộ ysoserial

Refer

https://www.infoworld.com/article/2072752/the-java-serialization-algorithm-revealed.html
https://learn.snyk.io/lessons/insecure-deserialization/java/
https://sec.vnpt.vn/2020/02/co-gi-ben-trong-cac-gadgetchain/
https://tsublogs.wordpress.com/2023/02/16/javasecurity101-2-java-deserialization-overview/
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java