يمكن لصفحات الويب العادية استخدام واجهات برمجة تطبيقات fetch()
أو XMLHttpRequest
لإرسال البيانات واستلامها من الخوادم البعيدة،
ولكن تكون محدودة وفقًا لسياسة المصدر نفسها. تبدأ النصوص البرمجية للمحتوى الطلبات
نيابةً عن مصدر الويب الذي تم إدخال النص البرمجي للمحتوى فيه، وبالتالي تخضع النصوص البرمجية للمحتوى أيضًا لسياسة المصدر نفسه. إنّ مصادر الإضافات ليست محدودة. ويمكن للنص البرمجي الذي يتم تنفيذه في مشغّل خدمات الإضافات أو علامة تبويب المقدّمة التواصل مع الخوادم البعيدة خارج نطاق المصدر، ما دامت الإضافة تطلب أذونات من مصادر متعددة.
مصدر الإضافة
تتوفّر كل إضافة قيد التشغيل ضمن مصدر أمان منفصل خاص بها. يمكن للإضافة طلب fetch()
للحصول على موارد ضمن تثبيتها بدون طلب امتيازات إضافية. على سبيل المثال، إذا كانت إحدى الإضافات تحتوي على ملف إعدادات JSON باسم config.json
في مجلد
config_resources/
، يمكن للإضافة استرداد محتوى الملف على النحو التالي:
const response = await fetch('/config_resources/config.json');
const jsonData = await response.json();
إذا حاولت الإضافة استخدام مصدر أمان آخر غير نفسها، مثلاً https://www.google.com، سيمنع المتصفّح ذلك ما لم تطلب الإضافة الأذونات المناسبة من مصادر متعددة.
طلب أذونات من مصادر متعددة
لطلب الوصول إلى الخوادم البعيدة خارج مصدر الإضافة، أضِف مضيفين أو أنماط مطابقة أو كليهما إلى قسم host_permissions في ملف manifest.
{
"name": "My extension",
...
"host_permissions": [
"https://www.google.com/"
],
...
}
يمكن أن تكون قيم الأذونات من مصادر مختلفة أسماء مضيفين مؤهَّلة بالكامل، مثل:
- "https://www.google.com/"
- "https://www.gmail.com/"
أو يمكن أن تكون أنماط مطابقة، مثل ما يلي:
- "https://*.google.com/"
- "https://*/"
يسمح نمط المطابقة "https://*/" بالوصول إلى HTTPS في جميع النطاقات التي يمكن الوصول إليها. يُرجى العِلم أنّ أنماط المطابقة تشبه أنماط مطابقة نصوص المحتوى، ولكن يتم تجاهل أي معلومات مسار تتبع المضيف.
يُرجى العلم أيضًا أنّه يتم منح إذن الوصول حسب المضيف والمخطّط. إذا كانت الإضافة تريد الوصول إلى مضيف أو مجموعة مضيفين معيّنين من خلال بروتوكول HTTP الآمن وغير الآمن، يجب الإفصاح عن الأذونات بشكل منفصل:
"host_permissions": [
"http://www.google.com/",
"https://www.google.com/"
]
دالة Fetch() مقابل دالة XMLHttpRequest()
تم إنشاء fetch()
خصيصًا لبرامج الخدمة، وتتّبع اتجاهًا أوسع على الويب يقضي بالابتعاد عن العمليات المتزامنة. تتوفّر واجهة برمجة التطبيقات XMLHttpRequest()
في الإضافات خارج مشغّل الخدمات، ويؤدي استدعاؤها إلى تنشيط معالِج الجلب لمشغّل خدمات الإضافة. يجب أن يفضّل العمل الجديد "fetch()
" كلما أمكن ذلك.
الاعتبارات الأمنية
تجنُّب الثغرات الأمنية في هجمات النصوص البرمجية على المواقع الإلكترونية
عند استخدام الموارد التي تم استردادها عبر fetch()
، يجب الحرص على عدم الوقوع ضحية البرمجة النصية على مواقع إلكترونية متعددة عند استخدام المستندات خارج الشاشة أو اللوحة الجانبية أو النافذة المنبثقة. وبشكل خاص، تجنَّب استخدام واجهات برمجة التطبيقات الخطيرة، مثل
innerHTML
. على سبيل المثال:
const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// WARNING! Might be injecting a malicious script!
document.getElementById("resp").innerHTML = jsonData;
...
بدلاً من ذلك، يمكنك تفضيل واجهات برمجة التطبيقات الأكثر أمانًا التي لا تشغّل النصوص البرمجية:
const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// JSON.parse does not evaluate the attacker's scripts.
let resp = JSON.parse(jsonData);
const response = await fetch("https://api.example.com/data.json");
const jsonData = response.json();
// textContent does not let the attacker inject HTML elements.
document.getElementById("resp").textContent = jsonData;
حظر الوصول إلى النصوص البرمجية للمحتوى على الطلبات الواردة من عدّة مصادر
عند تنفيذ طلبات من مصادر متعددة نيابةً عن نص برمجي للمحتوى، احرص على الحماية من صفحات الويب الضارة التي قد تحاول انتحال هوية نص برمجي خاص بالمحتوى. لا تسمح بشكل خاص بالنصوص البرمجية للمحتوى أن تطلب عنوان URL عشوائيًا.
لنفترض أنّ إحدى الإضافات تُجري طلبًا من مصدر مختلف للسماح لنص برمجي للمحتوى بمعرفة سعر سلعة معيّنة. من الطرق غير الآمنة جدًا أن يحدّد نص المحتوى المورد الدقيق الذي ستسترجعه الصفحة التي تعمل في الخلفية.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.contentScriptQuery == 'fetchUrl') {
// WARNING: SECURITY PROBLEM - a malicious web page may abuse
// the message handler to get access to arbitrary cross-origin
// resources.
fetch(request.url)
.then(response => response.text())
.then(text => sendResponse(text))
.catch(error => ...)
return true; // Will respond asynchronously.
}
}
);
chrome.runtime.sendMessage(
{
contentScriptQuery: 'fetchUrl',
url: `https://another-site.com/price-query?itemId=${encodeURIComponent(request.itemId)}`
},
response => parsePrice(response.text())
);
في النهج أعلاه، يمكن للنص البرمجي للمحتوى أن يطلب من الإضافة جلب أي عنوان URL يمكن للإضافة الوصول إليه. قد تتمكّن صفحة ويب ضارة من تزوير هذه الرسائل وخداع الإضافة لكي تمنح الإذن بالوصول إلى موارد من مصادر متعددة.
بدلاً من ذلك، يمكنك تصميم معالجات رسائل تحدّ من الموارد التي يمكن جلبها. في ما يلي، لا يوفّر نص المحتوى سوى
itemId
وليس عنوان URL الكامل.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.contentScriptQuery == 'queryPrice') {
const url = `https://another-site.com/price-query?itemId=${encodeURIComponent(request.itemId)}`
fetch(url)
.then(response => response.text())
.then(text => parsePrice(text))
.then(price => sendResponse(price))
.catch(error => ...)
return true; // Will respond asynchronously.
}
}
);
chrome.runtime.sendMessage(
{contentScriptQuery: 'queryPrice', itemId: 12345},
price => ...
);
تفضيل HTTPS على HTTP
بالإضافة إلى ذلك، يجب توخي الحذر بشكل خاص من الموارد التي يتم استرجاعها من خلال HTTP. إذا تم استخدام إضافتك على شبكة معادية، يمكن لمهاجم الشبكة (المعروف أيضًا باسم "man-in-the-middle") تعديل الردّ وربما مهاجمة إضافتك. بدلاً من ذلك، يُفضّل استخدام HTTPS كلما أمكن ذلك.
تعديل سياسة أمان المحتوى
إذا عدَّلت سياسة أمان المحتوى التلقائية للإضافة من خلال إضافة السمة content_security_policy
إلى البيان، ستحتاج إلى التأكّد من السماح لأي مضيفات تريد الاتصال بها. على الرغم من أنّ السياسة التلقائية لا تحدّ من عمليات الربط بالمضيفين،
يجب الحذر عند إضافة أي من التوجيهَين connect-src
أو default-src
بشكل صريح.