Tuy là những lỗ hổng của phần cứng nhưng chúng lại tác động rất lớn đến công tác phát triển phần mềm. Trước khi các lỗ hổng kênh kề này xuất hiện, hầu hết các nhà phát triển web chỉ cần tập trung vào bảo mật ứng dụng, nhưng kể từ khi cặp lỗ hổng nổi tiếng Spectre - Meltdown được công bố, họ phải xem xét cả cách trình duyệt quản lý bộ nhớ của nó trong chu trình xử lý yêu cầu.
Cả Meltdown và Spectre đều cho phép một tiến trình đọc bộ nhớ mà thông thường nó không có khả năng đọc. Đôi khi, nhiều tài liệu từ các trang web khác nhau có thể dẫn đến việc chia sẻ một tiến trình trong trình duyệt. Điều này có thể xảy ra khi trước đó đã mở trang web khác bằng window.open hoặc <a href="..." target="_blank"> hay iframe. Nếu một trang web chứa dữ liệu riêng của người dùng, một trang web khác có thể sử dụng các lỗ hổng mới này để đọc dữ liệu của người dùng đó. Bài báo này sẽ giới thiệu một số tính năng mà các lập trình viên có thể sử dụng để giảm thiểu tác động của cặp lỗ hổng này.
Site Isolation và Cross-Origin Read Blocking
Tác động của việc khai thác thành công Spectre có thể được giảm đáng kể bằng cách ngăn chặn dữ liệu nhạy cảm chia sẻ tiến trình với mã do kẻ tấn công kiểm soát. Chrome đã triển khai một tính năng có tên là Site Isolation (Cách ly trang web) để thực hiện điều đó. Isolation Site bổ sung thêm một ranh giới giữa các trang web bằng cách đảm bảo rằng, các trang từ các website khác nhau hoạt động trong các tiến trình “hộp cát” khác nhau trong trình duyệt. Vì mỗi trang web trong trình duyệt có quy trình riêng biệt, trong trường hợp lỗ hổng trình duyệt hoặc Spectre như lỗ hổng kênh kề, tính năng này sẽ gây khó khăn trong việc truy cập hay đánh cắp dữ liệu xuyên trang của các tài khoản người dùng trên các trang web khác.
Tuy nhiên, ngay cả khi tất cả các trang được đưa vào tiến trình riêng biệt thì chúng vẫn có thể yêu cầu một số dữ liệu phụ của trang web khác, chẳng hạn như hình ảnh và JavaScript. Một trang web độc hại có thể dùng một thẻ <img> để tải một tệp JSON với dữ liệu nhạy cảm như số dư tài khoản ngân hàng:
<img src="https://your-bank.example/balance.json">
Nội dung của tệp JSON sẽ được đưa vào bộ nhớ của tiến trình kết xuất nội dung hiển thị và không phải là hình ảnh. Nhưng kẻ tấn công có thể lợi dụng một lỗ hổng như Spectre để đọc vùng nhớ đó. Hoặc thay vì dùng thẻ <img>, kẻ tấn công có thể dùng thẻ <script>:
<script src="https://your-bank.example/balance.json"></script>
Để giúp ngăn chặn thông tin nhạy cảm bị rò rỉ, Site Isolation bao gồm tính năng Cross-Origin Read Blocking để chặn việc truyền dữ liệu giữa các trang web. CORB sẽ ngăn việc đưa nội dung của tệp balance.json vào bộ nhớ của tiến trình kết xuất nội dung hiển thị dựa trên kiểu MIME của nó.
Để đảm bảo mức an ninh cao nhất và hưởng lợi từ CORB, người dùng cần thực hiện những điều sau:
- Với các tài nguyên HTML, JSON và XML: Đảm bảo các tài nguyên này được cung cấp với "Content-Type" chính xác như danh sách dưới đây, cùng với thông tin điều khiển "X-Content-Type-Options: nosniff":
HTML MIME type - "text/html"
XML MIME type - "text/xml", "application/xml" hay bất kỳ kiểu MIME nào mà kiểu con kết thúc bằng "+xml"
JSON MIME type - "text/json", "application/json" hay bất kỳ kiểu MIME nào mà kiểu con kết thúc bằng "+json"
Ngoài ra, không nên hỗ trợ các yêu cầu multipart cho các tài nguyên nhạy cảm vì việc thay đổi kiểu MIME thành multipart/byteranges khiến cho Chrome khó bảo vệ dữ liệu hơn.
- Với các dạng tài nguyên khác (ví dụ như PDF, ZIP, PNG): Đảm bảo những tài nguyên này chỉ được cung cấp cho các yêu cầu có chứa token CSRF không thể đoán được (phân phối qua các tài nguyên được bảo vệ HTML, JSON hay XML như trình bày ở trên).
Về cơ bản, Site Isolation là một tính năng của trình duyệt không tác động trực tiếp đến những người phát triển ứng dụng web. Các lập trình viên không phải học thêm API mới nào và nhìn chung thì các trang web không thể nhận ra sự khác biệt khi chạy ở chế độ Site Isolation thông thường. Tuy nhiên, vẫn có một vài hiệu ứng phụ mà các lập trình viên cần lưu ý.
Bố cục toàn trang không còn đồng bộ
Với Site Isolation, bố cục toàn trang không còn được đảm bảo là đồng bộ, vì các khung của trang hiện có thể được chia ra nhiều tiến trình khác nhau. Điều này có thể ảnh hưởng đến các trang nếu người phát triển cho rằng thay đổi bố cục lan truyền ngay lập tức đến tất cả các khung trên trang.
Ví dụ: Một trang web có tên fluffykittens.example giao tiếp với một tiện ích xã hội được lưu trữ trên mạng xã hội - widget.example:
<!-- https://fluffykittens.example/ -->
<iframe src="https://social-widget.example/" width="123"></iframe>
<script>
const iframe = document.querySelector('iframe');
iframe.width = 456;
iframe.contentWindow.postMessage(
// The message to send:
'Meow!',
// The target origin:
'https://social-widget.example'
);
</script>
Thoạt đầu, độ rộng của <iframe> là 123 điểm ảnh. Nhưng sau đó trang FluffyKittens thay đổi độ rộng thành 456 điểm ảnh và gửi một thông điệp đến social widget, trong đó chứa đoạn mã sau:
<!-- https://social-widget.example/ -->
<script>
self.onmessage = () => {
console.log(document.documentElement.clientWidth);
};
</script>
Khi social widget nhận được một thông điệp qua postMessage API, nó sẽ ghi lại độ rộng của phần tử gốc <html>. Giá trị nào sẽ được ghi? Trước khi bật tính năng Site Isolation, câu trả lời là 456 vì mọi thứ được đồng bộ. Tuy nhiên, khi bật tính năng Site Isolation, việc bố cục lại social widget xảy ra không đồng bộ trong một tiến trình khác. Vì thế, câu trả lời sau đó sẽ là 123. Trong trường hợp này, cách làm tốt hơn là thiết lập độ rộng của khung cha và phát hiện sự thay đổi trong <iframe> bằng cách theo dõi sự kiện resize (thay đổi kích cỡ).
Các trình xử lý dỡ tải có thể bị quá giờ thường xuyên hơn
Khi một khung được chuyển hướng hoặc đóng lại, tài liệu cũ cùng với bất kỳ tài liệu khung con nào được nhúng trong đó đều chạy trình xử lý dỡ tải của chúng. Nếu điều hướng mới xảy ra trong cùng một tiến trình kết xuất (ví dụ: đối với điều hướng cùng nguồn gốc), trình xử lý dỡ tải của tài liệu cũ và các khung con của nó có thể chạy trong một thời gian khá dài trước khi cho phép điều hướng xảy ra.
addEventListener('unload', () => {
doSomethingThatMightTakeALongTime();
});
Trong tình huống này, trình xử lý dỡ tải trong tất cả các khung đều rất đáng tin cậy. Tuy nhiên, ngay cả khi không có tính năng Site Isolation thì một số việc điều hướng khung là xảy ra xuyên tiến trình, gây ảnh hưởng đến hoạt động của trình xử lý dỡ tải. Ví dụ, khi người dùng chuyển từ trang old.example sang new.example bằng cách gõ URL vào thanh địa chỉ, việc tải new.example diễn ra trong một tiến trình mới. Trình xử lý dỡ tải của old.example và các khung con của nó chạy trong tiến trình ngầm, sau khi trang new.example được hiển thị, các trình xử lý dỡ tải cũ được chấm dứt nếu chúng không hoàn thành công việc sau một thời gian nhất định. Vì các trình xử lý dỡ tải có thể không hoàn thành đúng thời hạn và bị ép chấm dứt hoạt động nên các hành vi dỡ tải sẽ kém tin cậy hơn.
Một hệ quả khác của việc áp dụng tính năng Site Isolation là việc xử lý song song của các trình xử lý dỡ tải: Trước đây các trình xử lý dỡ tải chạy theo một trật tự nghiêm ngặt từ trên xuống dưới cho các khung, khi bật Site Isolation, các trình xử lý dỡ tải chạy song song trong các tiến trình khác nhau.
Cách gửi thông báo “dỡ tải” hợp lý là dùng navigator.sendBeacon:
addEventListener('pagehide', () => {
navigator.sendBeacon('/end-of-session');
});
Nếu muốn kiểm soát tốt hơn, chúng ta có thể dùng tùy chọn keepalive của Fetch API:
addEventListener('pagehide', () => {
fetch('/end-of-session', { keepalive: true });
});
Các cookie SameSite
Các cookie thường được gửi cho tất cả các yêu cầu tới website thiết lập cookie, thậm chí cả trong trường hợp yêu cầu được một bên thứ ba gửi bằng thẻ <img>. SameSite là một thuộc tính mới, chỉ định rằng chỉ đính kèm cookie tới các yêu cầu xuất phát từ cùng site. Tuy nhiên, chỉ có những phiên bản mới nhất của các trình duyệt hỗ trợ SameSite (trong đó Internet Explorer 11 chỉ hỗ trợ một phần).
HTTPOnly và document.cookie
Nếu cookie của trang web chỉ được sử dụng phía máy chủ, không phải bởi JavaScript khách, có nhiều cách người dùng có thể ngăn dữ liệu của cookie vào quy trình hiển thị trang web (render process). Người dùng có thể đặt thuộc tính cookie HTTPOnly, điều này ngăn chặn cookie khỏi bị truy cập thông qua tập lệnh phía máy khách trên các trình duyệt được hỗ trợ, chẳng hạn như Chrome. Nếu không thể cài đặt HTTPOnly, người dùng có thể giúp hạn chế việc tải dữ liệu cookie vào quy trình được hiển thị (rendered process) bằng cách không đọc document.cookie trừ khi thực sự cần thiết.
Mở các liên kết ngoài bằng rel="noopener"
Khi liên kết đến một trang khác bằng target="_ blank", trang đã mở có quyền truy cập vào đối tượng cửa sổ của người dùng, có thể điều hướng trang đó đến một URL khác và nếu không có tính năng Site Isolation, nó sẽ ở trong cùng một tiến trình với trang của người dùng. Để bảo vệ trang được tốt hơn, các liên kết đến các trang bên ngoài mở trong một cửa sổ mới phải luôn chỉ định rel="noopener".
Giảm độ chính xác của bộ đếm thời gian
Để khai thác lỗ hổng Meltdown hoặc Spectre, kẻ tấn công cần đo thời gian để đọc một giá trị nhất định từ bộ nhớ. Điều này cần một bộ đếm thời gian đáng tin cậy và chính xác. API mà nền tảng web cung cấp performance.now() có chính xác đến 5 micro giây. Để giảm thiểu, tất cả các trình duyệt chính đã giảm độ phân giải của performance.now() để khiến việc tấn công trở nên khó khăn hơn.
Một cách khác để có được bộ đếm thời gian độ phân giải cao là sử dụng SharedArrayBuffer. Bộ đệm được sử dụng bởi một “công nhân” chuyên dụng để tăng bộ đếm. Các luồng chính đọc bộ đếm này và sử dụng nó như là một bộ đếm thời gian. Hiện tại, các trình duyệt đã quyết định vô hiệu hóa SharedArrayBuffer cho đến khi có các biện pháp giảm thiểu rủi ro khác.
Phát triển phần mềm không chỉ là lập trình
Quá trình phát triển phần mềm không chỉ bao gồm lập trình mà còn có một giai đoạn rất quan trọng khác là kiểm thử. Cặp lỗ hổng Spectre - Meltdown nhắc cho đội ngũ kiểm thử, nhất là kiểm thử an ninh rằng, chúng đã “giấu mình” suốt 20 năm trước khi bị phát hiện, trong khi trên lý thuyết chúng có thể bị phát hiện bất cứ lúc nào. Kiểm thử tự động đã không thể phát hiện ra cặp lỗ hổng vì cách làm đó chỉ thích hợp cho việc phát hiện những vấn đề mà đội ngũ phát triển biết là có thể xảy ra.
Những người phát hiện lỗ hổng Spectre hay Meltdown tìm ra chúng khi kiểm thử thăm dò (exploratory testing). Dù kiểm thử thăm dò không chắc có thể phát hiện ra những vấn đề ẩn, nhưng nó có thể tìm ra những tác động không mong muốn mà những vấn đề đó gây ra với các ứng dụng. Việc bổ sung kiểm thử thăm dò vào quy trình kiểm thử có thể giúp phát hiện các vấn đề tiềm ẩn sớm hơn trong tương lai.