דפי אינטרנט רגילים יכולים להשתמש באובייקט XMLHttpRequest כדי לשלוח ולקבל נתונים משרתים מרוחקים, אבל הם מוגבלים על ידי מדיניות אותו המקור. סקריפטים של תוכן יוזמים בקשות בשם מקור האינטרנט שאליו הוחדר הסקריפט של התוכן, ולכן סקריפטים של תוכן כפופים גם למדיניות של אותו מקור. (סקריפטים של תוכן כפופים ל-CORB מגרסה Chrome 73 ול-CORS מגרסה Chrome 83). המקורות של התוספים לא כל כך מוגבלים – סקריפט שמופעל בדף הרקע או בכרטיסייה הפעילה של תוסף יכול לתקשר עם שרתים מרוחקים מחוץ למקור שלו, כל עוד התוסף מבקש הרשאות חוצות-מקורות.
מקור התוסף
כל תוסף שפועל נמצא במקור אבטחה נפרד משלו. בלי לבקש הרשאות נוספות, התוסף יכול להשתמש ב-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, הדפדפן לא מאפשר זאת אלא אם התוסף ביקש את ההרשאות המתאימות לגישה בין מקורות.
בקשת הרשאות לגישה ממקורות שונים
אם מוסיפים מארחים או דפוסי התאמה של מארחים (או את שניהם) לקטע permissions בקובץ manifest, התוסף יכול לבקש גישה לשרתים מרוחקים מחוץ למקור שלו.
{
"name": "My extension",
...
"permissions": [
"https://www.google.com/"
],
...
}
ערכי הרשאות חוצי-מקור יכולים להיות שמות מארחים מוגדרים במלואם, כמו אלה:
- "https://www.google.com/"
- "https://www.gmail.com/"
או שהם יכולים להיות דפוסי התאמה, כמו אלה:
- "https://*.google.com/"
- "https://*/"
תבנית התאמה של 'https://*/' מאפשרת גישת HTTPS לכל הדומיינים שאפשר להגיע אליהם. שימו לב: התבניות של match דומות לתבניות של התאמה של סקריפטים של תוכן, אבל המערכת מתעלמת מכל מידע על נתיב אחרי המארח.
חשוב גם לציין שהגישה ניתנת לפי מארח ולפי סכימה. אם תוסף רוצה גישה מאובטחת ולא מאובטחת ב-HTTP למארח נתון או לקבוצת מארחים, הוא צריך להצהיר על ההרשאות בנפרד:
"permissions": [
"http://www.google.com/",
"https://www.google.com/"
]
שיקולי אבטחה
איך נמנעים מפרצות אבטחה XSS (cross-site scripting)
כשמשתמשים במשאבים שאוחזרו באמצעות XMLHttpRequest, דף הרקע צריך להיזהר שלא ליפול קורבן לפרצת אבטחה XSS (cross-site scripting). ספציפית, צריך להימנע משימוש בממשקי API מסוכנים כמו אלה שמופיעים בהמשך:
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();
במקום זאת, מומלץ להשתמש בממשקי API בטוחים יותר שלא מריצים סקריפטים:
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 שרירותית.
נניח שתוסף מבצע בקשת CORS כדי לאפשר לסקריפט תוכן לגלות את המחיר של פריט. גישה אחת (לא מאובטחת) היא שסקריפט התוכן יציין את המשאב המדויק שדף הרקע צריך לאחזר.
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.