يمكن لصفحات الويب العادية استخدام واجهات برمجة التطبيقات 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 في ملف البيان.
{
"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
content_security_policy إلى ملف البيان، عليك التأكّد من السماح بأي مضيفين تريد الاتصال بهم. على الرغم من أنّ السياسة التلقائية لا تقيّد الاتصالات بالمضيفين، يجب توخي الحذر عند إضافة التوجيهَين connect-src أو default-src بشكل صريح.