Huỷ chuyển đổi tuần tự không an toàn

Danh mục OWASP: MASVS-CODE: Chất lượng mã

Tổng quan

Khi lưu trữ hoặc chuyển một lượng lớn dữ liệu đối tượng Java, việc chuyển đổi tuần tự dữ liệu trước thường hiệu quả hơn. Sau đó, dữ liệu sẽ trải qua quy trình chuyển đổi tuần tự hoá thành đối tượng của ứng dụng, hoạt động hoặc nhà cung cấp nhận dữ liệu và cuối cùng xử lý dữ liệu. Trong trường hợp bình thường, dữ liệu được chuyển đổi tuần tự rồi chuyển đổi ngược tuần tự mà không có sự can thiệp của người dùng. Tuy nhiên, mối quan hệ tin cậy giữa quy trình chuyển đổi tuần tự và đối tượng dự kiến có thể bị một tác nhân độc hại lợi dụng. Ví dụ: tác nhân này có thể chặn và thay đổi các đối tượng được chuyển đổi tuần tự. Điều này sẽ cho phép đối tượng xấu thực hiện các cuộc tấn công như từ chối dịch vụ (DoS), leo thang đặc quyền và thực thi mã từ xa (RCE).

Mặc dù lớp Serializable là một phương thức phổ biến để quản lý quá trình chuyển đổi tuần tự, nhưng Android có lớp riêng để xử lý quá trình chuyển đổi tuần tự, được gọi là Parcel. Bằng cách sử dụng lớp Parcel, dữ liệu đối tượng có thể được chuyển đổi tuần tự thành dữ liệu luồng byte và đóng gói vào Parcel bằng giao diện Parcelable. Điều này giúp việc vận chuyển hoặc lưu trữ Parcel hiệu quả hơn.

Tuy nhiên, bạn nên cân nhắc kỹ lưỡng khi sử dụng lớp Parcel, vì lớp này được xem là một cơ chế truyền IPC có hiệu suất cao, nhưng không nên dùng để lưu trữ các đối tượng được chuyển đổi tuần tự trong bộ nhớ liên tục cục bộ vì điều này có thể dẫn đến các vấn đề về khả năng tương thích dữ liệu hoặc mất dữ liệu. Khi cần đọc dữ liệu, bạn có thể dùng giao diện Parcelable để chuyển đổi Parcel thành dữ liệu đối tượng.

Có 3 vectơ chính để khai thác quá trình chuyển đổi tuần tự hoá trong Android:

  • Tận dụng giả định không chính xác của nhà phát triển rằng việc giải tuần tự hoá các đối tượng bắt nguồn từ một loại lớp tuỳ chỉnh là an toàn. Trên thực tế, mọi đối tượng do bất kỳ lớp nào cung cấp đều có khả năng bị thay thế bằng nội dung độc hại. Trong trường hợp xấu nhất, nội dung này có thể can thiệp vào trình tải lớp của cùng một ứng dụng hoặc các ứng dụng khác. Sự can thiệp này có dạng chèn các giá trị nguy hiểm mà theo mục đích của lớp, có thể dẫn đến việc đánh cắp dữ liệu hoặc chiếm đoạt tài khoản.
  • Khai thác các phương thức khử tuần tự được coi là không an toàn theo thiết kế (ví dụ: CVE-2023-35669, một lỗ hổng leo thang đặc quyền cục bộ cho phép chèn mã JavaScript tuỳ ý thông qua một vectơ khử tuần tự đường liên kết sâu)
  • Khai thác các lỗ hổng trong logic ứng dụng (ví dụ: CVE-2023-20963, một lỗ hổng leo thang đặc quyền cục bộ cho phép ứng dụng tải xuống và thực thi mã trong một môi trường có đặc quyền thông qua một lỗ hổng trong logic gói WorkSource của Android).

Tác động

Mọi ứng dụng giải tuần tự hoá dữ liệu tuần tự hoá không đáng tin cậy hoặc độc hại đều có thể dễ bị tấn công thực thi mã từ xa hoặc từ chối dịch vụ.

Rủi ro: Giải tuần tự hoá dữ liệu đầu vào không đáng tin cậy

Kẻ tấn công có thể khai thác việc thiếu quy trình xác minh gói trong logic ứng dụng để chèn các đối tượng tuỳ ý. Sau khi được chuyển đổi tuần tự, các đối tượng này có thể buộc ứng dụng thực thi mã độc hại, dẫn đến tình trạng từ chối dịch vụ (DoS), leo thang đặc quyền và thực thi mã từ xa (RCE).

Các loại tấn công này có thể tinh vi. Ví dụ: một ứng dụng có thể chứa một ý định chỉ mong đợi một tham số. Sau khi được xác thực, tham số này sẽ được chuyển đổi tuần tự. Nếu kẻ tấn công gửi một tham số bổ sung độc hại thứ hai, không mong muốn cùng với tham số mong muốn, thì điều này sẽ khiến tất cả các đối tượng dữ liệu được chèn bị huỷ tuần tự hoá vì ý định coi các tham số bổ sung là một Bundle. Người dùng độc hại có thể lợi dụng hành vi này để chèn dữ liệu đối tượng. Sau khi được chuyển đổi tuần tự, dữ liệu này có thể dẫn đến RCE, xâm phạm hoặc mất dữ liệu.

Giải pháp giảm thiểu

Tốt nhất là bạn nên giả định rằng mọi dữ liệu được chuyển đổi tuần tự đều không đáng tin cậy và có khả năng độc hại. Để đảm bảo tính toàn vẹn của dữ liệu được chuyển đổi tuần tự, hãy thực hiện các bước kiểm tra xác minh đối với dữ liệu để đảm bảo đó là lớp và định dạng chính xác mà ứng dụng mong đợi.

Một giải pháp khả thi có thể là triển khai mẫu dự đoán cho java.io.ObjectInputStream thư viện. Bằng cách sửa đổi mã chịu trách nhiệm về việc chuyển đổi tuần tự, bạn có thể đảm bảo rằng chỉ một tập hợp các lớp được chỉ định rõ ràng được chuyển đổi tuần tự trong ý định.

Kể từ Android 13 (cấp độ API 33), một số phương thức đã được cập nhật trong lớp Intent được coi là các phương thức thay thế an toàn hơn cho các phương thức cũ và hiện không được dùng nữa để xử lý bưu kiện. Những phương thức an toàn hơn về kiểu mới này, chẳng hạn như getParcelableExtra(java.lang.String, java.lang.Class)getParcelableArrayListExtra(java.lang.String, java.lang.Class), thực hiện các bước kiểm tra kiểu dữ liệu để phát hiện những điểm yếu không khớp có thể khiến ứng dụng gặp sự cố và có khả năng bị khai thác để thực hiện các cuộc tấn công leo thang đặc quyền, chẳng hạn như CVE-2021-0928.

Ví dụ sau đây minh hoạ cách triển khai một phiên bản an toàn của lớp Parcel:

Giả sử lớp UserParcelable triển khai Parcelable và tạo một thực thể dữ liệu người dùng, sau đó được ghi vào Parcel. Sau đó, bạn có thể dùng phương thức an toàn hơn về kiểu sau đây của readParcelable để đọc gói được chuyển đổi tuần tự:

Kotlin

val parcel = Parcel.obtain()
val userParcelable = parcel.readParcelable(UserParcelable::class.java.classLoader)

Java

Parcel parcel = Parcel.obtain();
UserParcelable userParcelable = parcel.readParcelable(UserParcelable.class, UserParcelable.CREATOR);

Trong ví dụ về Java ở trên, hãy lưu ý cách sử dụng UserParcelable.CREATOR trong phương thức. Tham số bắt buộc này cho phương thức readParcelable biết loại cần mong đợi và có hiệu suất cao hơn so với phiên bản hiện không dùng nữa của phương thức readParcelable.

Rủi ro cụ thể

Phần này tổng hợp các rủi ro đòi hỏi chiến lược giảm thiểu không theo chuẩn hoặc được giảm thiểu ở một số cấp độ SDK nhất định và được liệt kê ở đây chỉ để cho đủ.

Rủi ro: Giải tuần tự hoá đối tượng không mong muốn

Việc triển khai giao diện Serializable trong một lớp sẽ tự động khiến tất cả các kiểu phụ của lớp đã cho triển khai giao diện. Trong trường hợp này, một số đối tượng có thể kế thừa giao diện nêu trên, nghĩa là các đối tượng cụ thể không được dùng để chuyển đổi tuần tự vẫn sẽ được xử lý. Điều này có thể vô tình làm tăng bề mặt tấn công.

Giải pháp giảm thiểu

Nếu một lớp kế thừa giao diện Serializable, theo hướng dẫn của OWASP, phương thức readObject phải được triển khai như sau để tránh việc một tập hợp các đối tượng trong lớp có thể được chuyển đổi tuần tự:

Kotlin

@Throws(IOException::class)
private final fun readObject(in: ObjectInputStream) {
    throw IOException("Cannot be deserialized")
}

Java

private final void readObject(ObjectInputStream in) throws java.io.IOException {
    throw new java.io.IOException("Cannot be deserialized");
}

Tài nguyên