CodeGym /Các khóa học /JAVA 25 SELF /Bảo mật tuần tự hóa: thực tiễn tốt nhất

Bảo mật tuần tự hóa: thực tiễn tốt nhất

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Các thực tiễn tốt nhất cốt lõi cho tuần tự hóa an toàn

Tuần tự hóa giống như việc đóng gói hành lý ở sân bay: nếu bạn không biết bên trong có gì và giao vali cho ai, bạn có thể gặp một “bất ngờ” khó chịu ở chốt an ninh. Trong Java, tuần tự hóa giúp lưu và khôi phục đối tượng một cách dễ dàng, nhưng cũng mở cánh cửa cho cả một dải tấn công nếu dữ liệu đến từ nguồn không đáng tin.

Mối đe dọa kinh điển:

Tuần tự hóa trong Java có thể không an toàn. Nếu kẻ tấn công cung cấp một luồng độc hại, thì khi giải tuần tự hóa có thể xảy ra những hệ quả tồi tệ nhất: từ việc thay đổi trường đến thực thi mã không mong muốn. Đây không phải chuyện dọa dẫm trong sách vở — lịch sử Java thực sự đã ghi nhận các cuộc tấn công dựa trên cơ chế này.

Vì sao lại như vậy?

Bởi vì giải tuần tự hóa không chỉ là khôi phục giá trị các trường. Trong quá trình này một đối tượng hoàn chỉnh được tạo ra: có thể gọi các phương thức đặc biệt (ví dụ, readObject, readResolve), và đôi khi các điểm yếu trong mã có thể bị kích hoạt qua reflection. Đặc biệt nguy hiểm là các lớp từ thư viện bên thứ ba: một số lớp thực thi hành động ngay trong giai đoạn giải tuần tự. Vì vậy đừng bao giờ tin tưởng dữ liệu đã tuần tự hóa nhận từ bên ngoài.

Hãy dùng transient cho dữ liệu nhạy cảm

Nếu lớp của bạn có các trường chứa mật khẩu, token, khóa riêng hoặc thông tin nhạy cảm khác, hãy khai báo chúng là transient. Những dữ liệu này sẽ không đi vào luồng tuần tự hóa.

import java.io.Serializable;

public class User implements Serializable {
    private String username;
    private transient String password; // không được tuần tự hóa

    // ...constructor, getter, setter...
}

Điều gì xảy ra khi giải tuần tự? Trường password sẽ có giá trị mặc định (null đối với chuỗi). Điều này là tốt: mật khẩu sẽ không bị lưu vào tệp/không bị truyền qua mạng.

Khai báo rõ ràng serialVersionUID

Luôn chỉ định serialVersionUID một cách rõ ràng. Điều này giảm khả năng lỗi tương thích và tối thiểu hóa rủi ro thay thế lớp khi giải tuần tự.

private static final long serialVersionUID = 1L;

Vì sao điều này quan trọng đối với bảo mật? Nếu không chỉ định serialVersionUID, trình biên dịch sẽ tạo tự động dựa trên cấu trúc lớp. Điều này có thể dẫn tới những không khớp ngoài dự kiến và, về mặt lý thuyết, tạo cơ hội lạm dụng bằng cách thay thế lớp cùng tên nhưng cấu trúc khác.

Kiểm tra kiểu đối tượng khi giải tuần tự

Đừng tin tưởng những gì đến từ mạng hoặc tệp. Sau khi giải tuần tự, luôn kiểm tra rằng đối tượng nhận được có kiểu như mong đợi trước khi sử dụng.

Object obj = objectInputStream.readObject();
if (obj instanceof User) {
    User user = (User) obj;
    // làm việc an toàn với user
} else {
    // kiểu không mong đợi — ném ngoại lệ hoặc xử lý lỗi
}

Tại sao cần làm vậy? Luồng độc hại có thể chứa đối tượng của lớp khác cũng triển khai Serializable nhưng không phù hợp với logic nghiệp vụ của bạn.

Giới hạn các lớp có thể được giải tuần tự (ObjectInputFilter)

Bắt đầu từ Java 9, hãy dùng bộ lọc — ObjectInputFilter — để giới hạn tập lớp được phép giải tuần tự. Nó giống như kiểm soát danh sách vào cửa.

Ví dụ: thiết lập bộ lọc

import java.io.*;

ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
        "com.example.User;com.example.Address;!*"
);

ObjectInputStream in = new ObjectInputStream(inputStream);
in.setObjectInputFilter(filter);

Object obj = in.readObject(); // bây giờ chỉ User và Address mới được giải tuần tự

Bộ lọc này chỉ cho phép các lớp UserAddress trong ứng dụng của bạn. Tất cả lớp khác sẽ bị chặn — một ngoại lệ sẽ được ném ra. Điều đó giảm đáng kể rủi ro đối tượng độc hại lọt vào.

Đừng giải tuần tự dữ liệu từ nguồn không đáng tin

Quy tắc vàng: nếu bạn không chắc chắn về nguồn dữ liệu — đừng giải tuần tự. Ưu tiên các định dạng không thực thi mã khi phân tích cú pháp (ví dụ, JSON, XML với bộ phân tích an toàn).

Ví dụ thực hành tệ:

// Đừng bao giờ làm thế với dữ liệu từ internet!
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
Object obj = in.readObject(); // nguy hiểm!

Nên làm gì tốt hơn?

  • Sử dụng bộ phân tích JSON (ví dụ, Gson/Jackson) hoặc bộ phân tích XML có xác thực.
  • Nếu cần tuần tự hóa nhị phân — lọc lớp bằng ObjectInputFilter và kiểm tra kiểu (instanceof).

Hãy dùng các định dạng thay thế để trao đổi với hệ thống bên ngoài

Cho tích hợp, hãy sử dụng các định dạng không thực thi mã khi phân tích: JSON, XML, Protocol Buffers, v.v. Điều này gần như loại bỏ các cuộc tấn công qua giải tuần tự.

// Thay vì ObjectInputStream, hãy dùng bộ phân tích JSON
User user = gson.fromJson(jsonString, User.class);

Đừng lưu các đối tượng đã tuần tự hóa ở nơi công khai

Tệp chứa đối tượng đã tuần tự hóa có thể chứa dữ liệu nhạy cảm. Đừng lưu chúng trong thư mục công khai và hãy giới hạn quyền truy cập ở cấp hệ thống tệp.

Đừng dựa vào tuần tự hóa để kiểm soát tính toàn vẹn

Tuần tự hóa không đảm bảo tính toàn vẹn hoặc tính xác thực của dữ liệu. Hãy dùng chữ ký số, kiểm tra tổng (checksum) hoặc mã hóa nếu không cho phép bị chỉnh sửa.

2. Thực hành: ví dụ về ObjectInputFilter và minh họa lỗ hổng

Ví dụ lọc lớp

Giả sử chúng ta có lớp User:

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private transient String password;

    // ...constructor, getter, setter...
}

Bộ lọc chỉ cho phép User:

ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
        "com.example.User;!*"
);
in.setObjectInputFilter(filter);

Bây giờ, nếu ai đó cố đưa vào một đối tượng thuộc lớp khác, quá trình giải tuần tự sẽ kết thúc bằng lỗi.

Minh họa lỗ hổng tiềm ẩn

Lớp độc hại:

// Giả sử ai đó đưa vào một lớp như thế này
public class Evil implements java.io.Serializable {
    static {
        System.out.println("Mã độc đã được thực thi!");
        // ở đây có thể là bất cứ thứ gì...
    }
}

Nếu không lọc lớp, khi giải tuần tự có thể tạo ra đối tượng Evil, và bộ khởi tạo tĩnh sẽ chạy khi nạp lớp — đó đã là một cuộc tấn công thực thụ.

4. Các lỗi điển hình khi đảm bảo bảo mật tuần tự hóa

Lỗi số 1: Giải tuần tự không có lọc và kiểm tra kiểu. Nhiều lập trình viên đọc đối tượng từ luồng rồi ép kiểu ngay sang kiểu mong muốn. Điều đó mở toang cánh cửa cho tấn công. Hãy dùng ObjectInputFilter và kiểm tra kiểu qua instanceof.

Lỗi số 2: Lưu dữ liệu nhạy cảm mà không dùng transient. Nếu quên khai báo mật khẩu/khóa là transient, chúng sẽ vào luồng và có thể rò rỉ cùng tệp.

Lỗi số 3: Thiếu serialVersionUID. Không có serialVersionUID khai báo tường minh có thể dẫn tới lỗi tương thích bất ngờ và rủi ro liên quan đến việc thay thế lớp.

Lỗi số 4: Dùng tuần tự hóa để trao đổi với hệ thống bên ngoài. Tuần tự hóa nhị phân tiện trong nội bộ ứng dụng (ví dụ, cache) nhưng nguy hiểm khi trao đổi bên ngoài. Hãy ưu tiên JSON/XML/Proto với bộ phân tích an toàn.

Lỗi số 5: Bỏ qua tính toàn vẹn dữ liệu. Việc thay đổi byte trong tệp đã tuần tự hóa có thể không bị phát hiện. Hãy áp dụng chữ ký số, kiểm tra tổng hoặc mã hóa.

1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Vật Phẩm Pháp Sư: Gắn Nhãn Phiên Bản cho Kho Báu
Vật Phẩm Pháp Sư: Gắn Nhãn Phiên Bản cho Kho Báu
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Khách hàng CRM: Bảo đảm toàn vẹn dữ liệu khi tải lên
Khách hàng CRM: Bảo đảm toàn vẹn dữ liệu khi tải lên
1
Khảo sát/đố vui
, cấp độ , bài học
Không có sẵn
Cấu hình serialization
Cấu hình serialization
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION