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 hạn chế trong chính sách rất đơn giản: tập lệnh phải được chuyển ra ngoài dòng thành riêng biệt
Bạn phải chuyển đổi các tệp JavaScript, trình xử lý sự kiện cùng dòng để sử dụng addEventListener
và eval()
là
tắt.
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()
và eval
, chẳng hạn như new Function()
để tối ưu hoá hiệu suất và dễ dàng biểu đạt. Các thư viện tạo mẫu đang
đặc biệt ưa chuộng phong cách triển khai này. Trong khi một số (như Angular.js) hỗ trợ CSP
của hộp này, nhiều khung làm việc phổ biến vẫn chưa được cập nhật lên cơ chế tương thích với
tiện ích Thế giới ít hơn eval
. 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 hộp cát là một cơ chế an toàn để đưa các thư viện này vào dự án của bạn mà không ảnh hưởng đến tính bảo mật.
Tại sao nên dùng 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. Có một loạt API chrome.*
mạnh mẽ có thể ảnh hưởng nghiêm trọng đến quyền riêng tư và sự an toàn của người dùng; việc đánh cắp dữ liệu đơn giản là điều ít đáng lo ngại nhất.
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 trang 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 thông qua iframe
, chúng ta có thể truyền thông báo cho trang đó, cho phép trang đó hành động theo một cách nào đó dựa trên các thông báo đó và chờ trang đó trả về kết quả cho chúng ta. 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, bao gồm cả ứng dụng 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, hãy 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ê các 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, hãy 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 kê khai sẽ có dạng như sau:
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
Tải tệp trong hộp cát
Để làm điều gì đó thú vị với tệp hộp cát, chúng ta cần tải tệp đó trong ngữ cảnh mà mã của tiện ích có thể xử lý tệp đó. Tại đây, Sandbox.html đã được tải vào
trang tiện ích qua iframe
. Tệp javaScript của trang chứa mã sẽ 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à gọi postMessage()
trên contentWindow
. Thông điệp là một đối tượng
chứa 3 thuộc tính: context
, templateName
và command
. Chúng ta sẽ tìm hiểu về context
và command
trong giây lát.
service-worker.js:
chrome.action.onClicked.addListener(() => {
chrome.tabs.create({
url: 'mainpage.html'
});
console.log('Opened a tab with a sandboxed page!');
});
extension-page.js:
let counter = 0;
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('reset').addEventListener('click', function () {
counter = 0;
document.querySelector('#result').innerHTML = '';
});
document.getElementById('sendMessage').addEventListener('click', function () {
counter++;
let message = {
command: 'render',
templateName: 'sample-template-' + counter,
context: { counter: counter }
};
document.getElementById('theFrame').contentWindow.postMessage(message, '*');
});
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:
extension-page.html:
<!DOCTYPE html>
<html>
<head>
<script src="mainpage.js"></script>
<link href="styles/main.css" rel="stylesheet" />
</head>
<body>
<div id="buttons">
<button id="sendMessage">Click me</button>
<button id="reset">Reset counter</button>
</div>
<div id="result"></div>
<iframe id="theFrame" src="sandbox.html" style="display: none"></iframe>
</body>
</html>
sandbox.html:
<script id="sample-template-1" type="text/x-handlebars-template">
<div class='entry'>
<h1>Hello</h1>
<p>This is a Handlebar template compiled inside a hidden sandboxed
iframe.</p>
<p>The counter parameter from postMessage() (outer frame) is:
</p>
</div>
</script>
<script id="sample-template-2" type="text/x-handlebars-template">
<div class='entry'>
<h1>Welcome back</h1>
<p>This is another Handlebar template compiled inside a hidden sandboxed
iframe.</p>
<p>The counter parameter from postMessage() (outer frame) is:
</p>
</div>
</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']
.
Trả lại kết quả
Chúng tôi sẽ cung cấp mẫu này để bạn sử dụng bằng cách thiết lập một trình nghe thông báo chấp nhận các lệnh
từ trang tiện ích. 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 một 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 tiện ích để tiện ích có thể làm điều gì đó hữu ích với tiện ích sau này:
<script>
const templatesElements = document.querySelectorAll(
"script[type='text/x-handlebars-template']"
);
let templates = {},
source,
name;
// precompile all templates in this page
for (let i = 0; i < templatesElements.length; i++) {
source = templatesElements[i].innerHTML;
name = templatesElements[i].id;
templates[name] = Handlebars.compile(source);
}
// Set up message event handler:
window.addEventListener('message', function (event) {
const command = event.data.command;
const template = templates[event.data.templateName];
let result = 'invalid request';
// if we don't know the templateName requested, return an error message
if (template) {
switch (command) {
case 'render':
result = template(event.data.context);
break;
// you could even do dynamic compilation, by accepting a command
// to compile a new template instead of using static ones, for example:
// case 'new':
// template = Handlebars.compile(event.data.templateSource);
// result = template(event.data.context);
// break;
}
} else {
result = 'Unknown template: ' + event.data.templateName;
}
event.source.postMessage({ result: result }, event.origin);
});
</script>
Quay lại trang tiện ích, 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 ta sẽ chỉ phản hồi thông qua một thông báo, nhưng bạn hoàn toàn có thể sử dụng HTML này một cách an toàn trong giao diện người dùng của tiện ích. Chèn thông qua
innerHTML
không gây ra rủi ro bảo mật đáng kể vì chúng tôi tin tưởng vào nội dung được kết xuất
trong hộp cát.
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 bảo mật và tiện ích Chrome tại Google I/O 2012 đưa ra một số ví dụ điển hình về các kỹ thuật này trong thực tế và đáng để bạn dành 56 phút để xem.