سیستم افزونه Chrome خطمشی امنیتی محتوای (CSP) پیشفرض نسبتاً سختگیرانهای را اعمال میکند.
محدودیتهای خطمشی ساده هستند: اسکریپت باید از حالت درونخطی خارج شود و به فایلهای جاوا اسکریپت جداگانه منتقل شود، کنترلکنندههای رویداد درونخطی باید برای استفاده از addEventListener تبدیل شوند، و eval() غیرفعال است.
بااینحال، میدانیم که کتابخانههای مختلفی از eval() و ساختارهای مشابه eval مانند
new Function() برای بهینهسازی عملکرد و سهولت بیان استفاده میکنند. کتابخانههای الگوبرداری
بهویژه مستعد این سبک پیادهسازی هستند. While some (like Angular.js) support CSP out
of the box, many popular frameworks haven't yet updated to a mechanism that is compatible with
extensions' eval-less world. بنابراین، حذف پشتیبانی از این عملکرد برای توسعهدهندگان مشکلسازتر از حد انتظار بوده است.
این سند جعبه امن را بهعنوان سازوکاری ایمن برای گنجاندن این کتابخانهها در پروژههایتان بدون بهخطر انداختن امنیت معرفی میکند.
چرا جعبه شنی؟
eval در داخل افزونه خطرناک است زیرا کدی که اجرا میکند به همه چیز در محیط بااجازه بالای افزونه دسترسی دارد. تعداد زیادی میانای برنامهسازی کاربردی browser.* قدرتمند دردسترس است که میتواند
بهشدت بر امنیت و حریم خصوصی کاربر تأثیر بگذارد؛ استخراج ساده دادهها کمترین نگرانی ما است.
راهکار پیشنهادی یک جعبه شنی است که در آن eval میتواند کد را بدون دسترسی به دادههای افزونه یا میاناهای برنامهسازی کاربردی ارزشمند افزونه اجرا کند. بدون داده، بدون میانای برنامهسازی کاربردی، بدون مشکل.
این کار را با فهرست کردن فایلهای HTML خاص در بسته افزونه بهعنوان فایلهای جعبه امن انجام میدهیم.
هرگاه صفحه محدودشدهای بار شود، به مبدأ یکتایی منتقل خواهد شد و دسترسی به میاناهای برنامهسازی کاربردی browser.* برای آن رد خواهد شد. If we load this sandboxed page into our extension via an iframe, we can
pass it messages, let it act upon those messages in some way, and wait for it to pass us back a
result. این سازوکار پیامرسانی ساده هرآنچه را که برای گنجاندن ایمن کد مبتنی بر eval در گردش کار افزونهمان نیاز داریم دراختیار ما قرار میدهد.
ایجاد و استفاده از محیط امن
اگر میخواهید مستقیماً وارد کد شوید، افزونه نمونه جعبه امن را بگیرید و شروع کنید. این یک نمونه کار از یک API پیامرسانی کوچک است که بر روی کتابخانه قالببندی Handlebars ساخته شده است و باید هر آنچه را که برای شروع نیاز دارید به شما بدهد. برای کسانی از شما که توضیح بیشتری میخواهند، بیایید باهم آن نمونه را در اینجا بررسی کنیم.
فهرست کردن فایلها در مانیفست
هر فایلی که باید در محیط امن اجرا شود باید با افزودن ویژگی sandbox در مانیفست افزونه فهرست شود. این مرحلهای حیاتی است و بهراحتی فراموش میشود، بنابراین دوباره بررسی کنید که
فایل سندباکسشده شما در مانیفست فهرست شده باشد. در این نمونه، ما فایل را در جعبه امن قرار میدهیم
که بهطور هوشمندانهای «sandbox.html» نامگذاری شده است. ورودی مانیفست به این شکل است:
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
بار کردن فایل جعبه امن
برای انجام کاری جالب با فایل محدودشده، باید آن را در زمینهای بار کنیم که
کد افزونه بتواند به آن دسترسی داشته باشد. در اینجا، sandbox.html بااستفاده از iframe در صفحه افزونه بار شده است. فایل جاوا اسکریپت صفحه حاوی کدی است که هرگاه با پیدا کردن iframe در صفحه و فراخوانی postMessage() در contentWindow آن، روی کنش مرورگر کلیک شود، پیامی به محیط امن ارسال میکند. پیام شیئی است که حاوی سه ویژگی است: context، templateName، و command. بهزودی به context و command خواهیم پرداخت.
service-worker.js:
browser.action.onClicked.addListener(() => {
browser.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, '*');
});
انجام دادن کاری خطرناک
وقتی sandbox.html بار میشود، کتابخانه Handlebars را بار میکند و الگوی درونخطی را به روشی که Handlebars پیشنهاد میکند ایجاد و کامپایل میکند:
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>
این کار ناموفق نمیشود! Even though Handlebars.compile ends up using new Function, things work
exactly as expected, and we end up with a compiled template in templates['hello'].
نتیجه را برگردانید
این الگو را با راهاندازی شنونده پیام که فرمانها را از صفحه افزونه میپذیرد برای استفاده دردسترس قرار میدهیم. We'll use the command passed in to determine what ought to be done (you could
imagine doing more than simply rendering; perhaps creating templates? شاید آنها را بهنحوی مدیریت کنید؟)، و context مستقیماً برای پردازش به الگو منتقل خواهد شد. HTML رندرشده
به صفحه افزونه برگردانده میشود تا افزونه بتواند بعداً کار مفیدی با آن انجام دهد:
<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>
Back in the extension page, we'll receive this message, and do something interesting with the html
data we've been passed. در این مورد، ما فقط آن را ازطریق یک اعلان تکرار میکنیم، اما
کاملاً ممکن است که از این HTML بهطور ایمن بهعنوان بخشی از واسط کاربر افزونه استفاده شود. درج آن ازطریق
innerHTML خطر امنیتی قابلتوجهی ایجاد نمیکند زیرا به محتوایی که در محیط امن ارائه شده است اعتماد داریم.
این سازوکار قالبسازی را ساده میکند، اما البته به قالبسازی محدود نمیشود. هر کدی که تحت «خطمشی امنیتی محتوای» سختگیرانه بهطور پیشفرض کار نکند میتواند در جعبه امن قرار گیرد؛ درواقع، اغلب مفید است که مؤلفههای افزونههایتان را که بهدرستی اجرا میشوند در جعبه امن قرار دهید تا هر بخش از برنامهتان را به کوچکترین مجموعه از امتیازات لازم برای اجرای صحیح آن محدود کنید. ارائه نوشتن برنامههای وب و افزونههای Chrome ایمن از Google I/O 2012 نمونههای خوبی از این تکنیک را در عمل ارائه میدهد و ارزش ۵۶ دقیقه از وقت شما را دارد.