سیستم برنامه افزودنی Chrome یک خطمشی امنیت محتوای پیشفرض نسبتاً سختگیرانه (CSP) را اعمال میکند. محدودیتهای خطمشی ساده هستند: اسکریپت باید خارج از خط به فایلهای جاوا اسکریپت جداگانه منتقل شود، کنترلکنندههای رویداد درون خطی باید برای استفاده از addEventListener
تبدیل شوند و eval()
غیرفعال است.
با این حال، میدانیم که کتابخانههای مختلفی از eval()
و ساختارهای شبیه eval
مانند new Function()
برای بهینهسازی عملکرد و سهولت بیان استفاده میکنند. کتابخانه های الگو به ویژه مستعد این سبک پیاده سازی هستند. در حالی که برخی (مانند Angular.js ) از CSP خارج از جعبه پشتیبانی میکنند، بسیاری از فریمورکهای محبوب هنوز به مکانیزمی که با دنیای eval
افزونهها سازگار باشد، بهروزرسانی نشدهاند. بنابراین حذف پشتیبانی از آن عملکرد برای توسعهدهندگان مشکلسازتر از حد انتظار بوده است.
این سند sandboxing را به عنوان مکانیزم ایمن برای گنجاندن این کتابخانه ها در پروژه های خود بدون به خطر انداختن امنیت معرفی می کند.
چرا سندباکس؟
eval
در داخل یک برنامه افزودنی خطرناک است زیرا کدی که اجرا می کند به همه چیز در محیط با مجوز بالا دسترسی دارد. تعداد زیادی chrome.*
استخراج ساده داده ها کمترین نگرانی ماست. راه حل ارائه شده یک جعبه شنی است که در آن eval
می تواند کد را بدون دسترسی به داده های برنامه افزودنی یا API های با ارزش بالای برنامه افزودنی اجرا کند. بدون داده، بدون API، بدون مشکل.
ما این کار را با فهرست کردن فایل های HTML خاص در داخل بسته برنامه افزودنی به عنوان جعبه شنی انجام می دهیم. هر زمان که یک صفحه sandbox بارگیری شود، به یک مبدا منحصر به فرد منتقل می شود و از دسترسی به chrome.*
API. اگر این صفحه سندباکس را از طریق iframe
در برنامه افزودنی خود بارگذاری کنیم، میتوانیم پیامهایی را به آن ارسال کنیم، اجازه دهیم به نحوی روی آن پیامها عمل کند و منتظر بمانیم تا نتیجه را به ما ارسال کند. این مکانیسم پیامرسانی ساده همه چیزهایی را که برای گنجاندن کدهای مبتنی بر eval
در گردش کار برنامه افزودنی نیاز داریم، به ما میدهد.
یک سندباکس ایجاد و استفاده کنید
اگر میخواهید مستقیماً وارد کد شوید، پسوند نمونه sandboxing را بردارید و بلند شوید . این یک نمونه کاربردی از یک API پیام کوچک است که در بالای کتابخانه قالب Handlebars ساخته شده است، و باید همه چیزهایی را که برای شروع به کار نیاز دارید در اختیار شما قرار دهد. برای کسانی از شما که میخواهید کمی توضیح بیشتری بدهید، بیایید این نمونه را با هم در اینجا مرور کنیم.
فایلها را در مانیفست فهرست کنید
هر فایلی که باید در یک جعبه ایمنی اجرا شود باید با افزودن یک ویژگی sandbox
در مانیفست پسوند فهرست شود. این یک مرحله حیاتی است و فراموش کردن آن آسان است، بنابراین دوباره بررسی کنید که فایل سندباکس شما در مانیفست فهرست شده باشد. در این نمونه، فایلی را با نام هوشمندانه "sandbox.html" sandbox می کنیم. ورودی مانیفست به شکل زیر است:
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
فایل سندباکس را بارگیری کنید
برای انجام کاری جالب با فایل sandboxed، باید آن را در زمینه ای بارگذاری کنیم که بتوان با کد افزونه به آن پرداخت. در اینجا، sandbox.html از طریق iframe
در یک صفحه افزونه بارگذاری شده است. فایل جاوا اسکریپت صفحه حاوی کدی است که با یافتن iframe
در صفحه و فراخوانی postMessage()
در contentWindow
، هر زمان که روی عملکرد مرورگر کلیک میشود، پیامی را به sandbox ارسال میکند. پیام یک شی حاوی سه ویژگی است: context
، templateName
و command
. ما در یک لحظه به context
و command
می پردازیم.
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, '*');
});
یه کار خطرناک انجام بده
هنگامی که 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>
این شکست نمی خورد! حتی اگر Handlebars.compile
از new Function
استفاده میکند، همه چیز دقیقاً همانطور که انتظار میرود کار میکند، و در نهایت با یک الگوی کامپایل شده در templates['hello']
مواجه میشویم.
نتیجه را به عقب برگردانید
ما این الگو را با راه اندازی یک شنونده پیام که دستورات صفحه افزونه را می پذیرد، برای استفاده در دسترس قرار می دهیم. ما از command
ارسال شده برای تعیین اینکه چه کاری باید انجام شود استفاده خواهیم کرد (شما می توانید تصور کنید که کاری بیش از رندر کردن ساده انجام دهید؛ شاید ایجاد الگوها؟ شاید آنها را به نحوی مدیریت کنید؟)، و 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>
در صفحه افزونه، ما این پیام را دریافت می کنیم و با داده های html
که به ما ارسال شده است، کار جالبی انجام می دهیم. در این مورد، ما فقط آن را از طریق یک اعلان بازتاب میدهیم، اما استفاده از این HTML بهعنوان بخشی از رابط کاربری افزونه کاملاً ممکن است. درج آن از طریق innerHTML
خطر امنیتی قابل توجهی ندارد زیرا ما به محتوای ارائه شده در جعبه ایمنی اعتماد داریم.
این مکانیسم قالب بندی را ساده می کند، اما البته به قالب بندی محدود نمی شود. هر کدی که تحت یک خطمشی امنیتی سختگیرانه محتوا کار نمیکند، میتواند در جعبه ایمنی قرار گیرد. در واقع، اغلب برای اجزای سندباکس افزونههای شما مفید است که به درستی اجرا میشوند تا هر بخش از برنامه شما را به کوچکترین مجموعهای از امتیازات لازم برای اجرای صحیح آن محدود کند. ارائه Writing Secure Web Apps and Chrome Extensions از Google I/O 2012 چند نمونه خوب از این تکنیک ها را در عمل ارائه می دهد و ارزش 56 دقیقه وقت شما را دارد.