Chơi một cách an toàn trong các IFrames hộp cát

Việc xây dựng trải nghiệm phong phú trên web ngày nay gần như không thể tránh khỏi bao gồm nhúng các thành phần và nội dung mà bạn không có quyền kiểm soát thực sự. Các tiện ích của bên thứ ba có thể thúc đẩy mức độ tương tác và đóng vai trò quan trọng trong việc tạo nên doanh thu tổng thể trải nghiệm người dùng và nội dung do người dùng tạo đôi khi còn quan trọng hơn so với nội dung gốc của trang web. Việc không thực hiện một trong hai việc đó không thực sự là một lựa chọn, nhưng đều làm tăng rủi ro xảy ra lỗi nội dung xấu trên trang web của bạn. Một mà bạn nhúng -- mỗi quảng cáo, mọi tiện ích truyền thông xã hội -- là một tiềm năng vectơ tấn công cho những kẻ có ý định xấu:

Chính sách bảo mật nội dung (CSP) có thể giảm thiểu rủi ro liên quan đến cả hai loại nội dung này bằng cách cung cấp cho cho bạn khả năng lập danh sách trắng các nguồn tập lệnh đáng tin cậy cụ thể và các nguồn nội dung. Đây là một bước quan trọng theo đúng hướng, nhưng cần lưu ý rằng khả năng bảo vệ mà hầu hết các lệnh CSP cung cấp là nhị phân: tài nguyên là được phép hoặc không được phép. Đôi khi sẽ có thể hữu ích khi nói "Tôi không thích tôi thực sự tin tưởng nguồn nội dung này, nhưng nó rất đẹp! Nhúng xin vui lòng, Trình duyệt, nhưng đừng để công cụ này làm hỏng trang web của tôi."

Đặc quyền thấp nhất

Về cơ bản, chúng tôi đang tìm kiếm một cơ chế cho phép chúng tôi cấp nội dung mà chúng tôi chỉ nhúng ở mức độ khả năng tối thiểu cần thiết để thực hiện công việc của mình. Nếu một tiện ích không cần bật cửa sổ mới, nên bạn không thể thu hồi quyền truy cập vào window.open tổn thương. Nếu trình bổ trợ này không yêu cầu Flash, thì việc tắt tính năng hỗ trợ trình bổ trợ không nên vấn đề. Chúng ta được đảm bảo an toàn nhất có thể nếu chúng ta tuân theo nguyên tắc tối thiểu đặc quyền và chặn từng tính năng không liên quan trực tiếp đến chức năng mà chúng tôi muốn để sử dụng. Kết quả là chúng ta không còn phải mù quáng tin tưởng rằng một số của nội dung được nhúng sẽ không tận dụng được những đặc quyền mà nội dung đó không nên sử dụng. Nó ban đầu sẽ không có quyền truy cập vào chức năng này.

Các phần tử iframe là bước đầu tiên để xây dựng một khung hợp lý cho một giải pháp như vậy. Việc tải một số thành phần không đáng tin cậy trong iframe sẽ cung cấp biện pháp phân tách giữa ứng dụng và nội dung bạn muốn tải. Nội dung hiển thị trong khung sẽ không có quyền truy cập vào DOM hoặc dữ liệu mà bạn đã lưu trữ cục bộ trên trang của bạn cũng như không có thể vẽ vào các vị trí tuỳ ý trên trang; điều này chỉ giới hạn ở phạm vi đường viền của khung. Tuy nhiên, sự phân tách này không thực sự rõ ràng. Trang được chứa vẫn có một số tuỳ chọn cho hành vi gây khó chịu hoặc độc hại: tự động phát video, trình bổ trợ và cửa sổ bật lên là phần nổi bật nhất.

Thuộc tính sandbox của phần tử iframe cung cấp cho chúng tôi những gì chúng tôi cần để thắt chặt các hạn chế đối với nội dung đóng khung. Chúng ta có thể hướng dẫn trình duyệt tải nội dung của một khung cụ thể ở đặc quyền thấp chỉ cho phép một nhóm nhỏ khả năng cần thiết để thực hiện bất kỳ công việc cần làm.

Vận động, nhưng xác minh

"Tweet" của Twitter là một ví dụ hay về chức năng có thể được nhúng an toàn trên trang web của bạn qua hộp cát. Twitter cho phép bạn nhúng thông qua iframe bằng mã sau:

<iframe src="https://platform.twitter.com/widgets/tweet_button.html"
        style="border: 0; width:130px; height:20px;"></iframe>

Để biết được chúng ta có thể khoá những gì, hãy cùng xem xét kỹ những khả năng nào mà nút yêu cầu. HTML được tải vào khung sẽ thực thi một chút JavaScript từ máy chủ của Twitter và tạo cửa sổ bật lên có chứa khi được nhấp vào. Giao diện đó cần quyền truy cập vào cookie để liên kết tweet với đúng tài khoản và cần khả năng để gửi biểu mẫu tweet. Thế là xong; khung hình này không cần tải mọi trình bổ trợ mà không cần điều hướng đến cửa sổ cấp cao nhất hay bất kỳ số bit khác của chức năng. Vì ứng dụng không cần những đặc quyền đó, hãy xoá chúng bằng cách tạo hộp cát cho nội dung của khung.

Tính năng hộp cát hoạt động dựa trên danh sách cho phép. Chúng tôi bắt đầu bằng việc xoá tất cả có thể, sau đó bật lại từng tính năng bằng cách thêm cờ cụ thể đối với cấu hình của hộp cát. Đối với tiện ích Twitter, chúng tôi quyết định bật JavaScript, cửa sổ bật lên, tính năng gửi biểu mẫu và trang web twitter.com cookie. Chúng ta có thể thực hiện việc này bằng cách thêm thuộc tính sandbox vào iframe bằng giá trị sau:

<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
    src="https://platform.twitter.com/widgets/tweet_button.html"
    style="border: 0; width:130px; height:20px;"></iframe>

Vậy là xong. Chúng tôi đã cung cấp cho khung này tất cả các tính năng cần thiết và trình duyệt sẽ từ chối một cách hữu ích quyền truy cập vào bất kỳ đặc quyền nào mà chúng tôi không cấp quyền truy cập rõ ràng thông qua giá trị của thuộc tính sandbox.

Kiểm soát chi tiết các chức năng

Chúng ta thấy một số cờ hộp cát khả thi trong ví dụ ở trên, Hãy tìm hiểu chi tiết hơn về hoạt động bên trong của thuộc tính này.

Khi iframe có thuộc tính hộp cát trống, tài liệu được đóng khung sẽ được đặt trong hộp cát hoàn toàn và tuân theo những hạn chế sau:

  • JavaScript sẽ không thực thi trong tài liệu được đóng khung. Điều này không chỉ bao gồm JavaScript được tải rõ ràng qua các thẻ tập lệnh, nhưng cũng có các trình xử lý sự kiện cùng dòng và javascript: URL. Điều này cũng có nghĩa là nội dung nằm trong thẻ noscript sẽ được hiển thị, chính xác như thể người dùng đã tự vô hiệu hoá tập lệnh.
  • Tài liệu đóng khung được tải theo một nguồn gốc riêng, tức là tất cả quy trình kiểm tra cùng nguồn gốc sẽ không thành công; nguồn gốc riêng biệt không khớp với nguồn gốc nào khác thậm chí là chính họ. Trong số các tác động khác, điều này có nghĩa là tài liệu không quyền truy cập vào dữ liệu được lưu trữ trong cookie của mọi nguồn gốc hoặc bất kỳ cơ chế lưu trữ nào khác (Bộ nhớ DOM, Cơ sở dữ liệu đã lập chỉ mục, v.v.).
  • Tài liệu được đóng khung không thể tạo cửa sổ hoặc hộp thoại mới (thông qua window.open hoặc target="_blank").
  • Không thể gửi biểu mẫu.
  • Trình bổ trợ sẽ không tải.
  • Tài liệu được đóng khung chỉ có thể tự điều hướng, chứ không thể điều hướng ở cấp độ cao nhất. Việc đặt window.top.location sẽ gửi một ngoại lệ và khi bạn nhấp vào đường liên kết có target="_top" sẽ không có hiệu lực.
  • Các tính năng kích hoạt tự động (thành phần biểu mẫu tự động lấy nét, tự động phát video, v.v.) đều bị chặn.
  • Không tìm được khoá con trỏ.
  • Thuộc tính seamless bị bỏ qua trên iframes mà tài liệu có khung chứa.

Thao tác này khá nghiêm ngặt và tài liệu được tải vào iframe hoàn toàn có hộp cát thực sự có rất ít rủi ro. Tất nhiên, việc này cũng không mang lại nhiều giá trị: bạn có thể kết thúc với hộp cát đầy đủ cho một số nội dung tĩnh, nhưng hầu hết bạn sẽ muốn nới lỏng mọi thứ một chút.

Ngoại trừ trình bổ trợ, bạn có thể gỡ bỏ từng hạn chế này bằng cách thêm cờ vào giá trị của thuộc tính hộp cát. Tài liệu hộp cát không bao giờ được chạy plugin, vì các plugin là mã gốc không có hộp cát, nhưng mọi thứ khác đều công bằng trò chơi:

  • allow-forms cho phép gửi biểu mẫu.
  • allow-popups cho phép cửa sổ bật lên (gây sốc!)
  • allow-pointer-lock cho phép khoá con trỏ (ngạc nhiên chưa!)
  • allow-same-origin cho phép tài liệu giữ nguyên nguồn gốc; các trang đã được tải từ https://example.com/ sẽ vẫn có quyền truy cập vào dữ liệu của nguồn gốc đó.
  • allow-scripts cho phép thực thi JavaScript, đồng thời hỗ trợ các tính năng tự động kích hoạt (vì chúng không đơn giản khi triển khai qua JavaScript).
  • allow-top-navigation cho phép tài liệu vượt ra khỏi khung bằng cách khi di chuyển đến cửa sổ cấp cao nhất.

Với những câu hỏi này, chúng tôi có thể đánh giá chính xác lý do cuối cùng nhóm cờ hộp cát trong ví dụ về Twitter ở trên:

  • allow-scripts là bắt buộc, vì trang được tải vào khung sẽ chạy một số JavaScript để xử lý tương tác của người dùng.
  • Cần có allow-popups vì nút này bật lên biểu mẫu tweet trong cửa sổ.
  • Cần có allow-forms vì biểu mẫu tweet phải là biểu mẫu có thể gửi.
  • allow-same-origin là cần thiết, vì cookie của twitter.com sẽ không hoạt động không thể truy cập và người dùng không thể đăng nhập để đăng biểu mẫu.

Một điều quan trọng cần lưu ý là cờ hộp cát được áp dụng cho một khung cũng áp dụng cho mọi cửa sổ hoặc khung được tạo trong hộp cát. Điều này có nghĩa là chúng tôi có để thêm allow-forms vào hộp cát của khung, mặc dù biểu mẫu này chỉ tồn tại trong cửa sổ có khung bật lên.

Khi thiết lập thuộc tính sandbox, tiện ích sẽ chỉ nhận được các quyền yêu cầu cũng như các chức năng như trình bổ trợ, thanh điều hướng trên cùng và khoá con trỏ vẫn bị chặn. Chúng tôi đã giảm rủi ro nhúng tiện ích này mà không ảnh hưởng xấu. Đây là một lợi ích cho tất cả những bên liên quan.

Tách biệt đặc quyền

Hộp cát cho nội dung của bên thứ ba để chạy mã không đáng tin cậy của họ trong một môi trường có đặc quyền thấp là điều khá rõ ràng có lợi. Thế còn có mã riêng không? Bạn tin tưởng chính mình, đúng không? Vậy thì tại sao bạn phải lo lắng về hộp cát?

Tôi sẽ xoay quanh câu hỏi: nếu mã của bạn không cần trình bổ trợ, tại sao lại quyền truy cập vào trình bổ trợ? Tốt nhất, đó là một đặc quyền mà bạn không bao giờ sử dụng, và tệ nhất cũng là một vectơ tiềm ẩn khiến kẻ tấn công xâm nhập vào cửa. Mã của mọi người có và trên thực tế mọi ứng dụng đều dễ bị lợi dụng theo một cách hoặc thiết bị khác. Việc hộp cát mã của riêng bạn có nghĩa là ngay cả khi kẻ tấn công thành công phá vỡ ứng dụng của bạn, chúng sẽ không được cấp quyền truy cập đầy đủ vào nguồn gốc của ứng dụng; chúng sẽ chỉ có thể làm những việc mà ứng dụng có thể làm. Vẫn tệ nhưng không đến mức tệ đó.

Bạn có thể giảm thiểu rủi ro hơn nữa bằng cách chia ứng dụng thành và tạo hộp cát cho từng phần với đặc quyền tối thiểu có thể. Kỹ thuật này rất phổ biến trong mã gốc: Chrome chẳng hạn, tự phá vỡ vào một quy trình trình duyệt có đặc quyền cao có quyền truy cập vào ổ đĩa cứng trên máy có thể tạo kết nối mạng cũng như nhiều quy trình kết xuất đồ hoạ có đặc quyền thấp thực hiện phần lớn việc phân tích cú pháp nội dung không đáng tin cậy. Trình kết xuất không cần phải chạm vào ổ đĩa, trình duyệt sẽ đảm nhận việc cung cấp cho họ tất cả thông tin cần thiết hiển thị một trang. Ngay cả khi một tin tặc khôn ngoan tìm cách phá hoại trình kết xuất, cô ta vẫn chưa đạt được kết quả tốt, vì trình kết xuất không thể thực hiện nhiều lợi ích: tất cả quyền truy cập có đặc quyền cao đều phải được định tuyến thông qua quy trình của trình duyệt. Những kẻ tấn công sẽ cần tìm một vài lỗ hổng trong các phần khác nhau của hệ thống bất kỳ thiệt hại nào, từ đó giúp giảm đáng kể nguy cơ giao hàng thành công.

Tạo hộp cát an toàn cho eval()

Với tính năng hộp cát và API postMessage, thành công của mô hình này khá đơn giản để áp dụng cho web. Các mảnh ứng dụng của bạn có thể nằm trong iframe hộp cát và tài liệu gốc có thể người môi giới giao tiếp giữa họ bằng cách đăng thông báo và lắng nghe phản hồi. Loại cấu trúc này đảm bảo rằng việc khai thác ở bất kỳ phần nào của ứng dụng sẽ gây thiệt hại tối thiểu có thể. Phương pháp này còn có lợi thế là buộc bạn phải tạo ra những điểm tích hợp rõ ràng để bạn biết chính xác mình cần ở đâu cẩn thận khi xác thực dữ liệu đầu vào và đầu ra. Hãy xem một ví dụ về đồ chơi, chỉ để xem chương trình đó hoạt động như thế nào.

Evalbox là một ứng dụng thú vị lấy một chuỗi và đánh giá chuỗi đó dưới dạng JavaScript. Ồ, phải không? Chính xác bạn đã chờ đợi bấy lâu nay. Điều này khá nguy hiểm cho ứng dụng của bạn, vì việc cho phép thực thi JavaScript tuỳ ý có nghĩa là bất kỳ và mọi dữ liệu mà một nguồn gốc cung cấp đều có thể lấy được. Chúng tôi sẽ giảm thiểu rủi ro Sự cố lỗiTM xảy ra khi đảm bảo rằng mã được thực thi bên trong một hộp cát, nên tính năng này an toàn hơn rất nhiều. Chúng ta sẽ đi qua mã từ từ trong ra ngoài, bắt đầu với nội dung của khung:

<!-- frame.html -->
<!DOCTYPE html>
<html>
    <head>
    <title>Evalbox's Frame</title>
    <script>
        window.addEventListener('message', function (e) {
        var mainWindow = e.source;
        var result = '';
        try {
            result = eval(e.data);
        } catch (e) {
            result = 'eval() threw an exception.';
        }
        mainWindow.postMessage(result, event.origin);
        });
    </script>
    </head>
</html>

Bên trong khung, chúng ta có một tài liệu nhỏ chỉ có chức năng nghe tin nhắn từ phần tử mẹ bằng cách kết nối với sự kiện message của đối tượng window. Bất cứ khi nào thành phần mẹ thực thi postMessage trên nội dung của iframe, sự kiện này sẽ kích hoạt, cho phép chúng ta truy cập vào chuỗi mà thành phần mẹ muốn thực thi.

Trong trình xử lý, chúng ta lấy thuộc tính source của sự kiện, đây là thuộc tính mẹ cửa sổ. Chúng tôi sẽ sử dụng mã này để gửi kết quả sao lưu nỗ lực sau khi xong. Sau đó, chúng ta sẽ thực hiện phần việc khó khăn nhất bằng cách chuyển dữ liệu được cung cấp vào eval(). Cuộc gọi này đã được gói trong một khối thử do các thao tác bị cấm bên trong một iframe hộp cát sẽ thường xuyên tạo ra ngoại lệ DOM; chúng tôi sẽ bắt và báo cáo một thông báo lỗi thân thiện. Cuối cùng, chúng tôi đăng kết quả quay lại cửa sổ chính. Đây là một nội dung khá đơn giản.

Thành phần mẹ cũng không phức tạp tương tự. Chúng ta sẽ tạo một giao diện người dùng nhỏ có textarea cho mã và button để thực thi, chúng ta sẽ lấy frame.html thông qua một tạo hộp cát iframe, chỉ cho phép thực thi tập lệnh:

<textarea id='code'></textarea>
<button id='safe'>eval() in a sandboxed frame.</button>
<iframe sandbox='allow-scripts'
        id='sandboxed'
        src='frame.html'></iframe>

Bây giờ, chúng ta sẽ kết nối để thực thi. Trước tiên, chúng ta sẽ lắng nghe phản hồi từ iframealert() cho người dùng của chúng ta. Có thể là một ứng dụng thực tế sẽ làm điều gì đó ít phiền toái hơn:

window.addEventListener('message',
    function (e) {
        // Sandboxed iframes which lack the 'allow-same-origin'
        // header have "null" rather than a valid origin. This means you still
        // have to be careful about accepting data via the messaging API you
        // create. Check that source, and validate those inputs!
        var frame = document.getElementById('sandboxed');
        if (e.origin === "null" &amp;&amp; e.source === frame.contentWindow)
        alert('Result: ' + e.data);
    });

Tiếp theo, chúng ta sẽ kết nối một trình xử lý sự kiện để nhấp vào button. Khi người dùng lượt nhấp, chúng tôi sẽ lấy nội dung hiện tại của textarea và truyền chúng vào khung để thực thi:

function evaluate() {
    var frame = document.getElementById('sandboxed');
    var code = document.getElementById('code').value;
    // Note that we're sending the message to "*", rather than some specific
    // origin. Sandboxed iframes which lack the 'allow-same-origin' header
    // don't have an origin which you can target: you'll have to send to any
    // origin, which might alow some esoteric attacks. Validate your output!
    frame.contentWindow.postMessage(code, '*');
}

document.getElementById('safe').addEventListener('click', evaluate);

Có dễ không? Chúng tôi đã tạo một API đánh giá rất đơn giản và chắc chắn rằng mã được đánh giá không có quyền truy cập vào thông tin nhạy cảm như cookie hoặc bộ nhớ DOM. Tương tự như vậy, mã được đánh giá không thể tải trình bổ trợ, bật cửa sổ mới, hoặc bất kỳ hoạt động gây khó chịu hoặc độc hại nào khác.

Bạn có thể làm tương tự với mã của riêng mình bằng cách chia các ứng dụng nguyên khối thành các thành phần mục đích duy nhất. Mỗi API có thể được gói trong một API nhắn tin đơn giản, chỉ cần giống như nội dung chúng tôi đã trình bày ở trên. Cửa sổ mẹ có đặc quyền cao có thể đóng vai trò là trình kiểm soát và điều phối viên, gửi thông báo vào các mô-đun cụ thể mà mỗi mô-đun có có ít đặc quyền nhất có thể để thực hiện công việc, lắng nghe kết quả và đảm bảo rằng mỗi mô-đun được cung cấp tốt chỉ với thông tin cần thiết.

Tuy nhiên, xin lưu ý rằng bạn cần phải thật cẩn thận khi xử lý nội dung có khung hình có cùng nguồn gốc với cấp độ gốc. Nếu một trang trên https://example.com/ tạo khung cho một trang khác trên cùng một nguồn gốc bằng một hộp cát bao gồm cả cờ allow-same-originallow-scripts, sau đó trang được đóng khung có thể truy cập vào trang gốc và xoá thuộc tính hộp cát hoàn toàn.

Chơi trong hộp cát của bạn

Bạn hiện có thể sử dụng công nghệ Hộp cát trên nhiều trình duyệt: Firefox 17 trở lên IE10+ và Chrome tại thời điểm viết bài (tất nhiên, tôi có các phiên bản bảng hỗ trợ). Đang áp dụng sandbox vào iframes mà bạn thêm vào sẽ cho phép bạn cấp một số đặc quyền nhất định cho nội dung mà chúng hiển thị, chỉ những đặc quyền cần thiết cho để nội dung hoạt động chính xác. Điều này giúp bạn có cơ hội giảm rủi ro liên quan đến việc bao gồm nội dung của bên thứ ba, bên trên và nhiều hơn những gì khả thi với giải pháp Bảo mật nội dung Chính sách.

Hơn nữa, hộp cát còn là một kỹ thuật mạnh mẽ giúp giảm nguy cơ kẻ tấn công sẽ có thể khai thác các lỗ hổng trong mã của chính bạn. Bằng cách phân tách một ứng dụng nguyên khối thành một tập hợp các dịch vụ hộp cát, mỗi dịch vụ chịu trách nhiệm cho một phần nhỏ chức năng độc lập, kẻ tấn công buộc phải không chỉ ảnh hưởng đến các khung hình cụ thể nội dung mà còn có thể kiểm soát. Đó là công việc khó khăn hơn nhiều, đặc biệt là vì bộ điều khiển có thể giảm đi đáng kể thuộc phạm vi áp dụng. Bạn có thể dành công sức liên quan đến bảo mật để kiểm tra đó nếu bạn hãy yêu cầu trình duyệt trợ giúp các bước còn lại.

Điều này không có nghĩa hộp cát là một giải pháp hoàn chỉnh cho vấn đề bảo mật trên Internet. Chế độ này bảo vệ theo chiều sâu và trừ phi bạn quyền kiểm soát đối với khách hàng, bạn chưa thể dựa vào hỗ trợ của trình duyệt cho tất cả người dùng của mình (nếu bạn kiểm soát khách hàng của người dùng -- môi trường doanh nghiệp, ví dụ như -- thật tuyệt!). Một ngày nào đó... nhưng hiện tại hộp cát là một lớp khác của bảo vệ nhằm tăng cường khả năng phòng thủ của bạn, đó không phải là khả năng bảo vệ hoàn toàn mà dựa vào đó bạn có thể hoàn toàn tin tưởng. Tuy nhiên, các lớp rất tuyệt vời. Tôi khuyên bạn nên tận dụng tính năng này một.

Tài liệu đọc thêm

  • "Tách biệt đặc quyền trong ứng dụng HTML5" là một bài viết thú vị hoạt động thông qua thiết kế của một khung làm việc nhỏ, và ứng dụng của nó vào ba ứng dụng HTML5 hiện có.

  • Quá trình hộp cát có thể linh hoạt hơn nữa khi được kết hợp với 2 iframe mới khác thuộc tính: srcdoc, và seamless. Đối tượng cũ cho phép bạn đưa nội dung vào một khung mà không yêu cầu HTTP và yêu cầu thứ hai cho phép tạo kiểu đi vào nội dung được đóng khung. Hiện tại, cả hai đều có mức hỗ trợ trình duyệt khá kém (Chrome và WebKit) ngủ ngon). nhưng sẽ là sự kết hợp thú vị trong tương lai. Bạn có thể ví dụ: nhận xét trong hộp cát về một bài viết qua mã sau:

        <iframe sandbox seamless
                srcdoc="<p>This is a user's comment!
                           It can't execute script!
                           Hooray for safety!</p>"></iframe>