صفحات وب معمولی میتوانند از APIهای 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، درخواست محتوا کند، این درخواست به عنوان یک درخواست cross-origin در نظر گرفته میشود، مگر اینکه افزونه مجوزهای میزبان را داشته باشد. درخواستهای cross-origin همیشه در اسکریپتهای محتوا به همین صورت در نظر گرفته میشوند، حتی اگر افزونه مجوزهای میزبان را داشته باشد.
درخواست مجوزهای بینمنبعی
برای درخواست دسترسی به سرورهای راه دور خارج از مبدا یک افزونه، hosts، match patterns یا هر دو را به بخش 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() به طور خاص برای سرویس ورکرها ایجاد شده است و روند وب گستردهتری را دنبال میکند که از عملیات همزمان فاصله میگیرد. API مربوط به XMLHttpRequest() در افزونههای خارج از سرویس ورکرها پشتیبانی میشود و فراخوانی آن، کنترلکنندهی fetch مربوط به سرویس ورکرها را فعال میکند. کارهای جدید باید تا حد امکان fetch() پشتیبانی کنند.
ملاحظات امنیتی
جلوگیری از آسیبپذیریهای اسکریپتنویسی بینسایتی
هنگام استفاده از منابع بازیابی شده از طریق fetch() ، سند خارج از صفحه، پنل کناری یا پنجره بازشو شما باید مراقب باشد که قربانی اسکریپت نویسی بین سایتی نشود. به طور خاص، از استفاده از API های خطرناک مانند 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;
...
در عوض، API های امنتری را ترجیح دهید که اسکریپت اجرا نمیکنند:
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 بازیابی میشوند. اگر افزونه شما در یک شبکهی متخاصم استفاده میشود، یک مهاجم شبکه (معروف به "مرد میانی" ) میتواند پاسخ را تغییر دهد و به طور بالقوه به افزونهی شما حمله کند. در عوض، تا حد امکان HTTPS را ترجیح دهید.
تنظیم سیاست امنیتی محتوا
اگر سیاست امنیتی محتوای پیشفرض افزونه خود را با اضافه کردن ویژگی content_security_policy به مانیفست خود تغییر دهید، باید مطمئن شوید که هر میزبان که میخواهید به آن متصل شوید، مجاز است. اگرچه سیاست پیشفرض، اتصال به میزبانها را محدود نمیکند، اما هنگام اضافه کردن صریح دستورالعملهای connect-src یا default-src مراقب باشید.