يفرض نظام الإضافات في Chrome سياسة أمان المحتوى (CSP) تلقائيًا صارمة إلى حد ما.
إنّ قيود السياسة واضحة: يجب نقل النص البرمجي خارج النص إلى ملفات JavaScript منفصلة، ويجب تحويل معالجات الأحداث المضمّنة لاستخدام addEventListener
، ويجب
إيقاف eval()
.
ومع ذلك، فإننا ندرك أن مجموعة متنوعة من المكتبات تستخدم التركيبات eval()
والتركيبات المشابهة لـ eval
مثل.
new Function()
لتحسين الأداء وسهولة التعبير. تكون مكتبات النماذج
معرضة بشكل خاص لهذا النمط من التنفيذ. على الرغم من أنّ بعض الإطارات (مثل Angular.js) تتيح استخدام CSP بدون أي إعدادات، لم يتم تعديل العديد من الإطارات الشائعة حتى الآن لتتوافق مع eval
في الإضافات. لذلك، تبيّن أنّ إزالة إمكانية استخدام هذه الوظيفة أكثر
إزعاجًا للمطوّرين مما كان متوقّعًا.
يقدّم هذا المستند وضع الحماية كآلية آمنة لتضمين هذه المكتبات في مشاريعك بدون التأثير في الأمان.
لماذا وضع الحماية؟
إنّ eval
خطير داخل إحدى الإضافات لأنّ الرمز البرمجي الذي تنفّذه يمكنه الوصول إلى كل شيء في
بيئة الإضافات التي تتمتع بأذونات عالية المستوى. يتوفر عدد كبير من واجهات برمجة تطبيقات chrome.*
الفعّالة التي يمكن
تؤثر بشدة في أمان المستخدم وخصوصيته؛ والاستخراج البسيط للبيانات هو أقل ما يقلقنا.
الحل المعروض هو وضع حماية يمكن من خلاله لـ eval
تنفيذ الرمز بدون الوصول إلى أي من
بيانات الإضافة أو واجهات برمجة التطبيقات العالية القيمة للإضافة. لا تتوفّر بيانات أو واجهات برمجة تطبيقات، ولكن لا داعي للقلق.
ونحقق ذلك من خلال إدراج ملفات HTML محددة داخل حزمة الإضافات باعتبارها في وضع الحماية.
عند تحميل صفحة في وضع الحماية، سيتم نقلها إلى مصدر فريد، وسيتم منعها
من الوصول إلى واجهات برمجة تطبيقات chrome.*
. إذا قمنا بتحميل الصفحة التي تم وضع الحماية لها في الإضافة عبر iframe
، فيمكننا
وتمريرها، والسماح لها بالتصرف بشأن هذه الرسائل بطريقة ما، ثم الانتظار حتى ترسل إلينا الرسالة
نتيجته. تمنحنا آلية المراسلة البسيطة هذه كل ما نحتاجه لتضمين رمز برمجي مستند إلى eval
بأمان في سير عمل الإضافة.
إنشاء وضع حماية واستخدامه
إذا كنت تريد البدء مباشرةً في استخدام الرموز البرمجية، يمكنك الحصول على نموذج إضافة وضع الحماية وبدء استخدامه. وهو مثال عملي لواجهة برمجة تطبيقات صغيرة للمراسلة تم إنشاؤها على مكتبة نماذج Handlebars، ومن المفترض أن يوفّر لك كل ما تحتاجه للبدء. إذا أردت معرفة المزيد من المعلومات، لنطّلِع على هذا المثال معًا.
إدراج الملفات في البيان
يجب إدراج كل ملف يجب تشغيله داخل مساحة محاكاة في بيان الإضافة من خلال إضافة سمة
sandbox
. هذه خطوة مهمة، ويسهل نسيانها، لذا تحقق جيدًا من أن
أن يتم إدراج الملف الذي تم وضع الحماية له في البيان. في هذا المثال، نضع الملف في مساحة مغلقة بذكاء
ويُسمى "sandbox.html". يبدو إدخال البيان كما يلي:
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
تحميل الملف في وضع الحماية
ولتنفيذ أمر مثير للاهتمام باستخدام الملف المحمي في وضع الحماية، نحتاج إلى تحميله في سياق حيث
فيمكن معالجتها من خلال رمز الإضافة. في هذه الحالة، تم تحميل sandbox.html في
صفحة إضافة من خلال iframe
. يحتوي ملف JavaScript للصفحة على رمز يُرسِل رسالة
إلى مساحة وضع الحماية كلما تم النقر على إجراء المتصفّح من خلال العثور على iframe
في الصفحة واستدعاء postMessage()
في contentWindow
. الرسالة عبارة عن كائن
وتتضمن ثلاث سمات: 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
، يتم تحميل مكتبة "المقاود" وإنشاء محتوى مضمّن وتجميعه.
قالب دقيق بالطريقة التي تقترح بها المقاود:
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
الذي تم تمريره لتحديد الإجراءات التي يجب اتّخاذها (يمكنك
تخيُّل تنفيذ إجراءات أكثر من مجرد التقديم، مثل إنشاء قوالب). Perhaps managing them in some
way?), and the context
will be passed into the template directly for rendering. رمز 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
خطرًا أمنيًا كبيرًا لأنّنا نثق بالمحتوى الذي تم عرضه
داخل مساحة وضع الحماية.
تجعل هذه الطريقة صياغة النماذج أمرًا بسيطًا، لكنها بالطبع لا تقتصر على إنشاء النماذج. يمكن وضع أي رمز مبرمَج في بيئة حماية برمجية إذا لم يعمل بشكل تلقائي بموجب سياسة صارمة لأمان المحتوى. وفي الواقع، من المفيد غالبًا وضع مكونات الإضافات التي ستتم إدارتها بشكل صحيح في بيئة حماية برمجية بهدف حصر كل جزء من برنامجك بأصغر مجموعة من الأذونات اللازمة لتنفيذه بشكل سليم. العرض التقديمي حول كتابة تطبيقات الويب الآمنة وإضافات Chrome من Google ويقدم مؤتمر I/O 2012 بعض الأمثلة الجيدة لهذه التقنية على أرض الواقع، وتستحق 56 دقيقة من الوقت.