يمكن لصفحات الويب العادية استخدام واجهات برمجة التطبيقات 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
بشكل صريح.