סקריפטינג חוצה אתרים (XSS) מבוסס-DOM מתרחש כשנתונים ממקור בשליטת המשתמש (כמו שם משתמש או כתובת URL להפניה שנלקחת מקטע כתובת ה-URL) מגיעים ליעד, שהוא פונקציה כמו eval() או מאפיין setter כמו .innerHTML שיכולים להריץ קוד JavaScript שרירותי.
DOM XSS היא אחת מהפגיעויות הנפוצות ביותר באבטחת אתרים, ולעתים קרובות צוותי פיתוח מכניסים אותה בטעות לאפליקציות שלהם. Trusted Types מספק לכם את הכלים לכתוב, לבדוק את האבטחה ולשמור על אפליקציות ללא פרצות אבטחה מסוג DOM XSS, על ידי אבטחה של פונקציות מסוכנות של Web API כברירת מחדל. סוגים מהימנים זמינים כפוליפיל לדפדפנים שעדיין לא תומכים בהם.
רקע
במשך שנים רבות, DOM XSS הייתה אחת מנקודות החולשה הנפוצות והמסוכנות ביותר באבטחת אתרים.
יש שני סוגים של פרצות אבטחה XSS (cross-site scripting). חלק מנקודות החולשה של XSS נגרמות בגלל קוד בצד השרת שיוצר את קוד ה-HTML שמרכיב את האתר בצורה לא מאובטחת. אחרות נובעות מבעיה בלקוח, שבה קוד JavaScript קורא לפונקציות מסוכנות עם תוכן שהמשתמש שולט בו.
כדי למנוע XSS בצד השרת, אל תיצרו HTML על ידי שרשור מחרוזות. במקום זאת, כדאי להשתמש בספריות תבניות עם בריחה אוטומטית בטוחה בהקשר, יחד עם מדיניות אבטחת תוכן מבוססת-nonce כדי לצמצם עוד יותר את הסיכון לבאגים.
עכשיו דפדפנים יכולים גם לעזור למנוע פרצות אבטחה מסוג XSS מבוסס-DOM בצד הלקוח באמצעות Trusted Types.
מבוא ל-API
התכונה 'סוגים מהימנים' פועלת על ידי נעילה של פונקציות מסוכנות של יעד. יכול להיות שחלק מהם כבר מוכרים לכם, כי ספקי דפדפנים ומסגרות אינטרנט כבר מונעים מכם להשתמש בתכונות האלה מסיבות אבטחה.
- שינוי סקריפט:
<script src>והגדרת תוכן טקסט של רכיבי<script>. - יצירת HTML ממחרוזת:
- הפעלת תוכן של תוסף:
- קומפילציה של קוד JavaScript בזמן ריצה:
evalsetTimeoutsetIntervalnew Function()
כדי להשתמש ב-Trusted Types, צריך לעבד את הנתונים לפני שמעבירים אותם לפונקציות האלה. השימוש רק במחרוזת נכשל, כי הדפדפן לא יודע אם הנתונים מהימנים:
anElement.innerHTML = location.href;
כדי לציין שהנתונים עברו עיבוד מאובטח, צריך ליצור אובייקט מיוחד – Trusted Type.
anElement.innerHTML = aTrustedHTML;
TrustedHTML
עבור sinks שמצפים לקטעי HTML. יש גם אובייקטים של TrustedScript ו-TrustedScriptURL עבור יעדים רגישים אחרים.
השימוש ב-Trusted Types מפחית באופן משמעותי את שטח הפנים של המתקפה מסוג DOM XSS באפליקציה. היא מפשטת את בדיקות האבטחה ומאפשרת לאכוף את בדיקות האבטחה מבוססות-הסוג שמתבצעות בזמן קומפילציה, ניתוח קוד או איגוד קוד בזמן ריצה בדפדפן.
איך משתמשים בסוגים מהימנים
הכנה לדוחות על הפרות של מדיניות אבטחת תוכן
אפשר להטמיע כלי לאיסוף דוחות, כמו reporting-api-processor או go-csp-collector בקוד פתוח, או להשתמש באחד מהכלים המסחריים המקבילים. אפשר גם להוסיף רישום ביומן בהתאמה אישית ולנפות הפרות באגים בדפדפן באמצעות ReportingObserver:
const observer = new ReportingObserver((reports, observer) => {
for (const report of reports) {
if (report.type !== 'csp-violation' ||
report.body.effectiveDirective !== 'require-trusted-types-for') {
continue;
}
const violation = report.body;
console.log('Trusted Types Violation:', violation);
// ... (rest of your logging and reporting logic)
}
}, { buffered: true });
observer.observe();
או על ידי הוספת פונקציית event listener:
document.addEventListener('securitypolicyviolation',
console.error.bind(console));
הוספת כותרת CSP לדיווח בלבד
מוסיפים את כותרת התגובה הבאה של HTTP למסמכים שרוצים להעביר ל-Trusted Types:
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
עכשיו כל ההפרות מדווחות ל-//my-csp-endpoint.example, אבל האתר ממשיך לפעול. בקטע הבא מוסבר איך פועל //my-csp-endpoint.example.
זיהוי הפרות של סוגים מהימנים
מעכשיו, בכל פעם ש-Trusted Types מזהה הפרה, הדפדפן שולח דוח ל-report-uri שהוגדר. לדוגמה, כשהאפליקציה שלכם מעבירה מחרוזת ל-innerHTML, הדפדפן שולח את הדוח הבא:
{
"csp-report": {
"document-uri": "https://my.url.example",
"violated-directive": "require-trusted-types-for",
"disposition": "report",
"blocked-uri": "trusted-types-sink",
"line-number": 39,
"column-number": 12,
"source-file": "https://my.url.example/script.js",
"status-code": 0,
"script-sample": "Element innerHTML <img src=x"
}
}
השורה הזו מציינת שבקובץ https://my.url.example/script.js בשורה 39, הפונקציה innerHTML נקראה עם המחרוזת שמתחילה ב-<img src=x. המידע הזה אמור לעזור לכם לצמצם את האפשרויות ולזהות אילו חלקים בקוד עלולים לגרום ל-DOM XSS וצריך לשנות.
תיקון ההפרות
יש כמה אפשרויות לתיקון הפרה של סוג מהימן. אפשר להסיר את הקוד הבעייתי, להשתמש בספרייה, ליצור מדיניות של סוג מהימן או, כמוצא אחרון, ליצור מדיניות ברירת מחדל.
לשכתב את הקוד הבעייתי
יכול להיות שהקוד שלא עומד בדרישות כבר לא נחוץ, או שאפשר לכתוב אותו מחדש בלי הפונקציות שגורמות להפרות:
el.textContent = ''; const img = document.createElement('img'); img.src = 'xyz.jpg'; el.appendChild(img);
el.innerHTML = '<img src=xyz.jpg>';
שימוש בספרייה
חלק מהספריות כבר יוצרות סוגים מהימנים שאפשר להעביר לפונקציות של sink. לדוגמה, אפשר להשתמש ב-DOMPurify כדי לבצע סניטציה לקטע קוד HTML ולהסיר ממנו מטען ייעודי (payload) של XSS.
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});
DOMPurify תומך בסוגים מהימנים ומחזיר HTML שעבר סניטציה ועטוף באובייקט TrustedHTML, כדי שהדפדפן לא ייצור הפרה.
יצירת מדיניות של סוגים מהימנים
לפעמים אי אפשר להסיר את הקוד שגורם להפרה, ואין ספריה שאפשר להשתמש בה כדי לבצע סניטציה של הערך וליצור סוג מהימן בשבילכם. במקרים כאלה, אתם יכולים ליצור אובייקט מסוג מהימן בעצמכם.
קודם כל, יוצרים מדיניות. מדיניות היא מפעל לייצור סוגים מהימנים שמחיל כללי אבטחה מסוימים על הקלט שלהם:
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
createHTML: string => string.replace(/\</g, '<')
});
}
הקוד הזה יוצר מדיניות בשם myEscapePolicy שיכולה ליצור אובייקטים של TrustedHTML באמצעות הפונקציה createHTML() שלה. הכללים המוגדרים מבצעים escape לתווים <
ב-HTML כדי למנוע יצירה של רכיבי HTML חדשים.
כך משתמשים במדיניות:
const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML); // true
el.innerHTML = escaped; // '<img src=x onerror=alert(1)>'
שימוש במדיניות ברירת מחדל
לפעמים אי אפשר לשנות את הקוד הבעייתי, למשל אם טוענים ספרייה של צד שלישי מ-CDN. במקרה כזה, צריך להשתמש במדיניות ברירת מחדל:
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
trustedTypes.createPolicy('default', {
createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
});
}
המדיניות שנקראת default משמשת בכל מקום שבו נעשה שימוש במחרוזת ב-sink שמקבל רק סוג מהימן.
מעבר לאכיפה של Content Security Policy
כשהאפליקציה שלכם לא מייצרת יותר הפרות, אתם יכולים להתחיל לאכוף Trusted Types:
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
עכשיו, לא משנה כמה מורכבת אפליקציית האינטרנט שלכם, הדבר היחיד שיכול ליצור פגיעות מסוג DOM XSS הוא הקוד באחד מכללי המדיניות שלכם, ואתם יכולים להגביל את זה עוד יותר על ידי הגבלת יצירת כללי מדיניות.