Lưu trữ an toàn dữ liệu người dùng trong các ứng dụng web hiện đại

David Dworken
David Dworken

Nhiều ứng dụng web cần hiển thị nội dung do người dùng kiểm soát. Việc này có thể đơn giản như phân phối hình ảnh do người dùng tải lên (ví dụ: ảnh hồ sơ) hoặc phức tạp như hiển thị HTML do người dùng kiểm soát (ví dụ: hướng dẫn phát triển web). Điều này luôn là điều khó khăn để thực hiện một cách an toàn, do đó chúng tôi đã nỗ lực tìm ra các giải pháp đơn giản nhưng an toàn có thể áp dụng cho hầu hết các loại ứng dụng web.

Các giải pháp cũ để cách ly nội dung không đáng tin cậy

Giải pháp cổ điển để phân phối nội dung do người dùng kiểm soát một cách an toàn là sử dụng miền hộp cát. Về cơ bản, nếu miền chính của ứng dụng là example.com, thì bạn có thể phân phát mọi nội dung không đáng tin cậy trên exampleusercontent.com. Vì 2 miền này hoạt động trên nhiều trang web, nên mọi nội dung độc hại trên exampleusercontent.com đều không ảnh hưởng đến example.com.
Phương pháp này có thể được dùng để phân phát an toàn mọi loại nội dung không đáng tin cậy, bao gồm hình ảnh, nội dung tải xuống và HTML. Mặc dù có vẻ không cần thiết phải sử dụng tính năng này cho hình ảnh hoặc nội dung tải xuống, nhưng làm như vậy sẽ giúp tránh nguy cơ phát hiện nội dung, đặc biệt là trong các trình duyệt cũ.
Miền hộp cát được sử dụng rộng rãi trong ngành và đã hoạt động hiệu quả trong một thời gian dài. Tuy nhiên, chúng có hai nhược điểm lớn:

  • Các ứng dụng thường cần phải giới hạn quyền truy cập nội dung cho một người dùng duy nhất, điều này yêu cầu phải triển khai quá trình xác thực và ủy quyền. Vì miền hộp cát chủ đích không chia sẻ cookie với miền của ứng dụng chính, nên việc này rất khó thực hiện một cách an toàn. Để hỗ trợ tính năng xác thực, các trang web phải dựa vào URL chức năng hoặc phải đặt cookie xác thực riêng cho miền hộp cát. Phương pháp thứ hai này đặc biệt khó khăn trong web hiện đại, nơi nhiều trình duyệt hạn chế cookie trên trang web theo mặc định.
  • Mặc dù nội dung của người dùng được tách biệt với trang web chính nhưng nội dung đó không tách biệt với nội dung khác của người dùng. Điều này tạo ra nguy cơ nội dung người dùng độc hại tấn công dữ liệu khác trên miền hộp cát (ví dụ: qua việc đọc dữ liệu cùng nguồn gốc).

Bạn cũng nên lưu ý rằng miền hộp cát giúp giảm thiểu rủi ro lừa đảo vì các tài nguyên được phân đoạn rõ ràng thành một miền riêng biệt.

Các giải pháp hiện đại để phân phối nội dung cho người dùng

Theo thời gian, web phát triển và giờ đây có nhiều cách dễ dàng và an toàn hơn để phân phát nội dung không đáng tin cậy. Có nhiều phương pháp tiếp cận ở đây, vì vậy, chúng tôi sẽ trình bày hai giải pháp hiện đang được sử dụng rộng rãi tại Google.

Phương pháp 1: Phân phát nội dung không hoạt động của người dùng

Nếu một trang web chỉ cần phân phát nội dung không hoạt động của người dùng (ví dụ: nội dung không phải là HTML hoặc JavaScript, ví dụ như hình ảnh và nội dung tải xuống), thì giờ đây bạn có thể thực hiện việc này một cách an toàn mà không cần miền hộp cát tách biệt. Có hai bước chính:

  • Luôn đặt tiêu đề Content-Type thành một loại MIME phổ biến được tất cả trình duyệt hỗ trợ và đảm bảo không chứa nội dung đang hoạt động (nếu không chắc thì application/octet-stream là một lựa chọn an toàn).
  • Ngoài ra, luôn đặt các tiêu đề phản hồi bên dưới để đảm bảo rằng trình duyệt hoàn toàn tách biệt phản hồi.
Tiêu đề phản hồi Mục đích

X-Content-Type-Options: nosniff

Ngăn chặn nội dung nghe lén

Content-Disposition: attachment; filename="download"

Kích hoạt tải xuống thay vì hiển thị

Content-Security-Policy: sandbox

Hộp cát nội dung như thể nội dung được phân phát trên một miền riêng biệt

Content-Security-Policy: default-src ‘none'

Tắt tính năng thực thi JavaScript (và bao gồm mọi tài nguyên phụ)

Cross-Origin-Resource-Policy: same-site

Không cho trang được đưa vào trên nhiều trang web

Sự kết hợp các tiêu đề này đảm bảo rằng phản hồi chỉ có thể được tải dưới dạng tài nguyên phụ bởi ứng dụng của bạn hoặc được người dùng tải xuống dưới dạng tệp. Hơn nữa, các tiêu đề cung cấp nhiều lớp bảo vệ khỏi các lỗi trình duyệt thông qua tiêu đề hộp cát CSP và quy định hạn chế default-src. Nhìn chung, thông tin thiết lập nêu trên cho thấy mức độ tin cậy cao rằng các phản hồi được phân phát theo cách này không thể dẫn đến các lỗ hổng bị chèn hoặc tách biệt.

Phòng thủ theo chiều sâu

Mặc dù giải pháp trên thường cho thấy một cơ chế bảo vệ đầy đủ trước XSS, nhưng bạn có thể áp dụng một số biện pháp tăng cường bổ sung để cung cấp thêm các lớp bảo mật:

  • Đặt tiêu đề X-Content-Security-Policy: sandbox để tương thích với IE11.
  • Đặt tiêu đề Content-Security-Policy: frame-ancestors 'none' để chặn nhúng điểm cuối.
  • Hộp cát nội dung người dùng trên một miền con tách biệt bằng cách:
    • Phân phát nội dung người dùng trên một miền con tách biệt (ví dụ: Google sử dụng các miền như product.usercontent.google.com).
    • Đặt Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp để bật tính năng tách biệt nhiều nguồn gốc.

Phương pháp 2: Phân phát nội dung đang hoạt động của người dùng

Bạn cũng có thể phân phát nội dung hoạt động một cách an toàn (ví dụ: hình ảnh HTML hoặc SVG) mà không có điểm yếu của phương pháp tiếp cận miền hộp cát cổ điển.
Lựa chọn đơn giản nhất là tận dụng tiêu đề Content-Security-Policy: sandbox để yêu cầu trình duyệt tách riêng phản hồi. Mặc dù hiện tại không phải tất cả trình duyệt web đều triển khai tách biệt quy trình cho tài liệu hộp cát, nhưng việc tiếp tục tinh chỉnh các mô hình quy trình trình duyệt có thể cải thiện việc tách nội dung trong hộp cát khỏi các ứng dụng nhúng. Nếu các cuộc tấn công SpectreJSxâm phạm trình kết xuất nằm ngoài mô hình mối đe doạ của bạn, thì việc sử dụng hộp cát CSP có thể là một giải pháp đủ hiệu quả.
Tại Google, chúng tôi đã phát triển một giải pháp có thể tách biệt hoàn toàn nội dung đang hoạt động không đáng tin cậy bằng cách hiện đại hoá khái niệm miền hộp cát. Ý tưởng cốt lõi là:

  • Tạo miền hộp cát mới để thêm vào danh sách hậu tố công khai. Ví dụ: bằng cách thêm exampleusercontent.com vào PSL, bạn có thể đảm bảo rằng foo.exampleusercontent.combar.exampleusercontent.com giao nhau trên nhiều trang web, do đó hoàn toàn tách biệt với nhau.
  • Tất cả các URL khớp với *.exampleusercontent.com/shim đều được định tuyến đến một tệp chèn tĩnh. Tệp shim này chứa một đoạn mã HTML và JavaScript ngắn theo dõi trình xử lý sự kiện message và hiển thị mọi nội dung nhận được.
  • Để sử dụng thuộc tính này, sản phẩm sẽ tạo iframe hoặc cửa sổ bật lên cho $RANDOM_VALUE.exampleusercontent.com/shim rồi sử dụng postMessage để gửi nội dung không tin cậy đến chèn quảng cáo để hiển thị.
  • Nội dung đã kết xuất sẽ được chuyển đổi thành Blob và kết xuất bên trong iframe trong hộp cát.

So với phương pháp tiếp cận miền hộp cát cổ điển, phương pháp này đảm bảo rằng tất cả nội dung được tách biệt hoàn toàn trên một trang web duy nhất. Đồng thời, bằng cách để ứng dụng chính xử lý việc truy xuất dữ liệu được hiển thị, không còn cần phải sử dụng URL chức năng nữa.

Kết luận

Hai giải pháp này kết hợp với nhau giúp bạn có thể di chuyển khỏi các miền hộp cát cũ như googleusercontent.com sang các giải pháp an toàn hơn tương thích với tính năng chặn cookie của bên thứ ba. Tại Google, chúng tôi đã di chuyển nhiều sản phẩm để sử dụng các giải pháp này và dự định di chuyển nhiều sản phẩm khác trong năm tới.