يمكن لصفحات الويب العادية استخدام العنصر XMLHttpRequest لإرسال البيانات وتلقّيها من خوادم بعيدة، ولكنّها تخضع لقيود سياسة المصدر نفسه. تبدأ نصوص المحتوى البرمجية الطلبات نيابةً عن مصدر الويب الذي تم إدخال نص المحتوى البرمجي فيه، وبالتالي تخضع نصوص المحتوى البرمجية أيضًا لسياسة المصدر نفسه. (تخضع النصوص البرمجية للمحتوى لسياسة حظر قراءة البيانات من مصادر متعددة (CORB) منذ الإصدار 73 من Chrome وسياسة مشاركة الموارد المشتركة المنشأ (CORS) منذ الإصدار 83 من Chrome). لا تكون مصادر الإضافات محدودة إلى هذا الحد، إذ يمكن لنص برمجي يتم تنفيذه في صفحة الخلفية أو علامة التبويب في المقدّمة الخاصة بإحدى الإضافات أن يتواصل مع خوادم بعيدة خارج مصدره، طالما أنّ الإضافة تطلب أذونات الوصول إلى مصادر متعددة.
مصدر الإضافة
تتوفّر كل إضافة قيد التشغيل ضمن مصدر أمان منفصل خاص بها. بدون طلب امتيازات إضافية، يمكن للإضافة استخدام XMLHttpRequest للحصول على موارد ضمن عملية التثبيت. على سبيل المثال، إذا كان أحد الإضافات يحتوي على ملف إعداد بتنسيق JSON باسم config.json، في مجلد config_resources، يمكن للإضافة استرداد محتوى الملف على النحو التالي:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = handleStateChange; // Implemented elsewhere.
xhr.open("GET", chrome.extension.getURL('/config_resources/config.json'), true);
xhr.send();
إذا حاولت الإضافة استخدام مصدر أمان آخر غير مصدرها، مثلاً https://www.google.com، سيمنعها المتصفح ما لم تطلب الإضافة أذونات مناسبة للوصول إلى مصادر متعددة النطاقات.
طلب أذونات من مصادر متعددة
من خلال إضافة مضيفين أو أنماط مطابقة للمضيفين (أو كليهما) إلى قسم الأذونات في ملف البيان، يمكن للإضافة طلب الوصول إلى خوادم بعيدة خارج مصدرها.
{
"name": "My extension",
...
"permissions": [
"https://www.google.com/"
],
...
}
يمكن أن تكون قيم أذونات المشاركة في المصادر المتعددة أسماء مضيف مؤهَّلة بالكامل، مثل ما يلي:
- "https://www.google.com/"
- "https://www.gmail.com/"
أو يمكن أن تكون أنماط مطابقة، مثل ما يلي:
- "https://*.google.com/"
- "https://*/"
يسمح نمط المطابقة "https://*/" بالوصول إلى جميع النطاقات التي يمكن الوصول إليها عبر HTTPS. يُرجى العِلم أنّ أنماط المطابقة هنا تشبه أنماط المطابقة الخاصة ببرامج النصوص الخاصة بالمحتوى، ولكن يتم تجاهل أي معلومات مسار تلي المضيف.
يُرجى العِلم أيضًا أنّه يتم منح إذن الوصول حسب المضيف وحسب المخطّط. إذا كانت الإضافة تريد الوصول إلى مضيف أو مجموعة مضيفين عبر بروتوكول HTTP الآمن وغير الآمن، عليها الإفصاح عن الأذونات بشكل منفصل:
"permissions": [
"http://www.google.com/",
"https://www.google.com/"
]
الاعتبارات الأمنية
تجنُّب الثغرات الأمنية في النصوص البرمجية على المواقع الإلكترونية
عند استخدام موارد يتم استردادها من خلال XMLHttpRequest، يجب أن تحرص صفحة الخلفية على عدم الوقوع ضحية البرمجة النصية على مواقع متعددة. على وجه التحديد، تجنَّب استخدام واجهات برمجة التطبيقات الخطيرة، مثل ما يلي:
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// WARNING! Might be evaluating an evil script!
var resp = eval("(" + xhr.responseText + ")");
...
}
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// WARNING! Might be injecting a malicious script!
document.getElementById("resp").innerHTML = xhr.responseText;
...
}
}
xhr.send();
بدلاً من ذلك، ننصحك باستخدام واجهات برمجة تطبيقات أكثر أمانًا لا تشغّل نصوصًا برمجية:
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// JSON.parse does not evaluate the attacker's scripts.
var resp = JSON.parse(xhr.responseText);
}
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// innerText does not let the attacker inject HTML elements.
document.getElementById("resp").innerText = xhr.responseText;
}
}
xhr.send();
تقييد وصول نصوص المحتوى البرمجية إلى الطلبات من مصادر خارجية
عند تنفيذ طلبات من مصادر متعددة نيابةً عن برنامج نصي للمحتوى، احرص على الحماية من صفحات الويب الضارة التي قد تحاول انتحال هوية برنامج نصي للمحتوى. على وجه الخصوص، لا تسمح لبرامج النصوص الخاصة بالمحتوى بطلب عنوان 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') {
var 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 بشكل صريح.