Sử dụng eval trong các tiện ích của Chrome

Hệ thống tiện ích của Chrome thực thi một Chính sách bảo mật nội dung (CSP) mặc định khá nghiêm ngặt. Các quy định hạn chế trong chính sách này rất đơn giản: tập lệnh phải được di chuyển ra khỏi dòng vào các tệp JavaScript riêng biệt, trình xử lý sự kiện nội tuyến phải được chuyển đổi để sử dụng addEventListenereval() bị tắt. Ứng dụng Chrome có chính sách nghiêm ngặt hơn nữa và chúng tôi khá hài lòng với việc bảo mật thuộc tính mà các chính sách này cung cấp.

Tuy nhiên, chúng tôi nhận thấy rằng nhiều thư viện sử dụng các cấu trúc giống như eval()eval, chẳng hạn như new Function() để tối ưu hoá hiệu suất và dễ dàng biểu đạt. Thư viện tạo mẫu đặc biệt dễ bị lỗi triển khai kiểu này. Mặc dù một số (như Angular.js) hỗ trợ CSP ngay từ đầu, nhưng nhiều khung phổ biến vẫn chưa cập nhật lên cơ chế tương thích với thế giới không có eval của các tiện ích. Do đó, việc xoá tính năng hỗ trợ đó đã chứng minh là gây ra nhiều vấn đề hơn dự kiến cho nhà phát triển.

Tài liệu này giới thiệu tính năng hộp cát như một cơ chế an toàn để đưa các thư viện này vào dự án mà không ảnh hưởng đến tính bảo mật. Tóm lại, chúng ta sẽ sử dụng toàn bộ thuật ngữ phần mở rộng, nhưng khái niệm này áp dụng như nhau cho các ứng dụng.

Tại sao là hộp cát?

eval nguy hiểm bên trong một tiện ích vì mã mà tiện ích này thực thi có quyền truy cập vào mọi nội dung trong môi trường có quyền cao của tiện ích. Hiện có nhiều API chrome.* mạnh mẽ có thể ảnh hưởng nghiêm trọng đến tính bảo mật và quyền riêng tư của người dùng; đánh cắp dữ liệu đơn giản là điều ít lo lắng nhất của chúng tôi. Giải pháp được cung cấp là một hộp cát trong đó eval có thể thực thi mã mà không cần quyền truy cập vào dữ liệu của tiện ích hoặc các API có giá trị cao của tiện ích. Không có dữ liệu, không có API, không có vấn đề.

Chúng tôi thực hiện việc này bằng cách liệt kê các tệp HTML cụ thể bên trong gói tiện ích dưới dạng hộp cát. Bất cứ khi nào một trang trong hộp cát được tải, trang đó sẽ được chuyển đến một nguồn gốc duy nhất và sẽ bị từ chối quyền truy cập vào các API chrome.*. Nếu tải trang hộp cát này vào tiện ích của mình qua iframe, chúng ta có thể chuyển tin nhắn, để công cụ hành động dựa trên những thông điệp đó theo cách nào đó và chờ đợi báo cáo chuyển lại cho chúng ta kết quả. Cơ chế nhắn tin đơn giản này cung cấp cho chúng tôi mọi thứ cần thiết để đảm bảo an toàn, kể cả dựa trên eval trong quy trình làm việc của tiện ích.

Tạo và sử dụng hộp cát.

Nếu bạn muốn bắt tay ngay vào lập trình, vui lòng tải tiện ích mẫu hộp cát và bắt đầu. Đây là ví dụ thực tế về một API nhắn tin nhỏ được xây dựng dựa trên thư viện mẫu Handlebars và sẽ cung cấp cho bạn mọi thứ cần thiết để bắt đầu. Dành cho những ai Một chút thông tin giải thích nữa, hãy cùng xem qua mẫu đó ở đây.

Liệt kê tệp trong tệp kê khai

Mỗi tệp lẽ ra phải được chạy bên trong một hộp cát phải được liệt kê trong tệp kê khai tiện ích bằng cách thêm một Thuộc tính sandbox. Đây là một bước quan trọng và dễ bị quên, vì vậy, vui lòng kiểm tra kỹ để đảm bảo rằng tệp hộp cát của bạn được liệt kê trong tệp kê khai. Trong mẫu này, chúng ta sẽ tạo hộp cát cho tệp có tên "sandbox.html". Mục nhập tệp kê khai có dạng như sau:

{
  ...,
  "sandbox": {
     "pages": ["sandbox.html"]
  },
  ...
}

Tải tệp trong hộp cát

Để thực hiện tác vụ thú vị với tệp hộp cát, chúng ta cần tải tệp trong ngữ cảnh có thể giải quyết được bằng mã của tiện ích. Tại đây, sandbox.html đã được tải vào trang Sự kiện của tiện ích (eventpage.html) qua iframe. eventpage.js chứa mã gửi thông báo vào hộp cát bất cứ khi nào người dùng nhấp vào hành động của trình duyệt bằng cách tìm iframe trên trang và thực thi phương thức postMessage trên contentWindow. Thông điệp là một đối tượng chứa hai thuộc tính: contextcommand. Chúng ta sẽ tìm hiểu cả hai phương pháp này trong giây lát.

chrome.browserAction.onClicked.addListener(function() {
 var iframe = document.getElementById('theFrame');
 var message = {
   command: 'render',
   context: {thing: 'world'}
 };
 iframe.contentWindow.postMessage(message, '*');
});
Để biết thông tin chung về API postMessage, hãy xem tài liệu về postMessage trên MDN . Bài viết này khá đầy đủ và đáng đọc. Cụ thể, lưu ý rằng bạn chỉ có thể truyền dữ liệu qua lại nếu dữ liệu đó có thể chuyển đổi tuần tự. Ví dụ: các hàm không phải là đối tượng.

Làm một việc nguy hiểm

Khi tải, sandbox.html sẽ tải thư viện Handlebars, đồng thời tạo và biên dịch một mẫu cùng dòng theo cách Handlebars đề xuất:

<script src="handlebars-1.0.0.beta.6.js"></script>
<script id="hello-world-template" type="text/x-handlebars-template">
  <div class="entry">
    <h1>Hello, !</h1>
  </div>
</script>
<script>
  var templates = [];
  var source = document.getElementById('hello-world-template').innerHTML;
  templates['hello'] = Handlebars.compile(source);
</script>

Cách này không thành công! Mặc dù Handlebars.compile kết thúc bằng cách sử dụng new Function, nhưng mọi thứ hoạt động đúng như dự kiến và chúng ta sẽ có một mẫu được biên dịch trong templates['hello'].

Truyền kết quả trở lại

Chúng ta sẽ cung cấp mẫu này để sử dụng bằng cách thiết lập trình nghe thông báo chấp nhận các lệnh từ Trang sự kiện. Chúng tôi sẽ sử dụng command được truyền vào để xác định việc cần làm (bạn có thể hãy hình dung bạn làm nhiều việc khác ngoài việc kết xuất hình ảnh; có thể là tạo mẫu không? Có thể quản lý chúng theo cách nào đó?), và context sẽ được truyền trực tiếp vào mẫu để hiển thị. HTML được kết xuất sẽ được trả về Trang sự kiện để tiện ích có thể làm điều gì đó hữu ích với tiện ích này sau này:

<script>
  window.addEventListener('message', function(event) {
    var command = event.data.command;
    var name = event.data.name || 'hello';
    switch(command) {
      case 'render':
        event.source.postMessage({
          name: name,
          html: templates[name](event.data.context)
        }, event.origin);
        break;

      // case 'somethingElse':
      //   ...
    }
  });
</script>

Quay lại Trang sự kiện, chúng ta sẽ nhận được thông báo này và làm một việc thú vị với dữ liệu html mà chúng ta đã được truyền. Trong trường hợp này, chúng tôi sẽ chỉ nhắc lại thông qua Thông báo trên màn hình, nhưng bạn hoàn toàn có thể sử dụng HTML này một cách an toàn như một phần trong giao diện người dùng của tiện ích. Việc chèn mã này thông qua innerHTML không gây ra rủi ro bảo mật đáng kể, vì ngay cả khi mã trong hộp cát bị xâm phạm hoàn toàn thông qua một số cuộc tấn công thông minh, thì mã này cũng không thể chèn tập lệnh hoặc nội dung trình bổ trợ nguy hiểm vào ngữ cảnh tiện ích có quyền cao.

Cơ chế này giúp việc tạo mẫu trở nên đơn giản, nhưng tất nhiên không chỉ giới hạn ở việc tạo mẫu. Mọi mã không hoạt động ngay lập tức theo Chính sách bảo mật nội dung nghiêm ngặt đều có thể được đưa vào hộp cát; thực tế, việc đưa các thành phần của tiện ích vào hộp cát thường hữu ích khi các thành phần đó sẽ chạy chính xác để hạn chế mỗi phần của chương trình ở nhóm đặc quyền nhỏ nhất cần thiết để thực thi đúng cách. Bài trình bày Viết ứng dụng web an toàn và tiện ích Chrome tại Google I/O 2012 đưa ra một số ví dụ điển hình về cách áp dụng các kỹ thuật này và đáng để bạn dành ra 56 phút.