בדפי אינטרנט רגילים אפשר להשתמש בממשקי ה-API של fetch()
או XMLHttpRequest
כדי לשלוח ולקבל נתונים משרתים מרוחקים, אבל הם מוגבלים על ידי אותה מדיניות מקור. סקריפטים של תוכן יוזמים בקשות מטעם מקור האינטרנט שאליו הוחדר סקריפט התוכן, ולכן גם סקריפטים של תוכן כפופים לאותה מדיניות מקור. מקורות התוספים לא כל כך מוגבלים. סקריפט שמופעל ב-service worker של תוסף או בכרטיסייה בחזית יכול לתקשר עם שרתים מרוחקים שלא נמצאים במקור שלו, כל עוד התוסף מבקש הרשאות ממקורות שונים.
מקור התוסף
כל תוסף פועל קיים במקור אבטחה נפרד. התוסף יכול להפעיל את fetch()
כדי לקבל משאבים במהלך ההתקנה, בלי לבקש הרשאות נוספות. לדוגמה, אם תוסף מכיל קובץ תצורה בשם 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()
נוצרה במיוחד עבור Service Worker ופועלת במגמה רחבה יותר באינטרנט, שלא קשורה לפעולות סינכרוניות. ממשק ה-API XMLHttpRequest()
נתמך בתוספים שמחוץ ל-Service Worker, וקריאה אליו מפעילה את מטפל האחזור של ה-Service Worker של התוסף. כל עבודה חדשה צריכה לתת עדיפות ל-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;
הגבלת הגישה של סקריפט התוכן לבקשות ממקורות שונים
כששולחים בקשות ממקורות שונים בשם סקריפט תוכן, חשוב להתגונן מפני דפי אינטרנט זדוניים שעלולים לנסות להתחזות לסקריפט של תוכן. באופן ספציפי, אל תאפשרו לסקריפטים של תוכן לבקש כתובת אתר שרירותית.
ניקח לדוגמה דוגמה שבה תוסף מבצע בקשה ממקורות שונים כדי לאפשר לסקריפט תוכן לגלות את המחיר של פריט. גישה לא כל כך מאובטחת היא שסקריפט התוכן יציין את המשאב המדויק שיש לאחזור על ידי דף הרקע.
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 שלתוסף יש גישה אליה. דף אינטרנט זדוני עלול לזייף הודעות כאלה ולהטעות את התוסף כדי שיעניק לתוסף גישה למשאבים ממקורות שונים.
במקום זאת, צריך ליצור רכיבי handler של הודעות שיגבילו את המשאבים שאפשר לאחזר. בהמשך, רק
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
.