פריצה מסוג DOM-based cross-site scripting (DOM XSS) מתרחשת כאשר נתונים ממקור שנמצא בשליטת המשתמש (כמו שם משתמש או כתובת URL להפניה אוטומטית שנלקחה מחלק מתוך כתובת ה-URL) מגיעים לנקודת יציאה, שהיא פונקציה כמו eval()
או מגדיר מאפיין כמו .innerHTML
שיכולים להריץ קוד JavaScript שרירותי.
DOM XSS היא אחת מנקודות החולשה הנפוצות ביותר באבטחת האינטרנט, וצוותי הפיתוח נוטים להכניס אותה בטעות לאפליקציות שלהם. Trusted Types מספקים את הכלים לכתיבה, לבדיקת אבטחה ולמניעת נקודות חולשה מסוג DOM XSS באפליקציות, על ידי אבטחת פונקציות מסוכנות של Web API כברירת מחדל. Trusted Types זמינים כpolyfill לדפדפנים שעדיין אין בהם תמיכה בהם.
רקע
במשך שנים רבות, DOM XSS הייתה אחת מנקודות החולשה הנפוצות והמסוכנות ביותר באבטחת האינטרנט.
יש שני סוגים של פרצות אבטחה מסוג XSS. חלק מנקודות החולשה של XSS נגרמות כתוצאה מקוד בצד השרת שיוצר את קוד ה-HTML שמרכיב את האתר באופן לא מאובטח. בחלק מהמקרים, הסיבה העיקרית נמצאת בצד הלקוח, שבו קוד ה-JavaScript קורא לפונקציות מסוכנות עם תוכן שנשלט על ידי המשתמש.
כדי למנוע XSS בצד השרת, אל תיצרו HTML על ידי שרשור מחרוזות. במקום זאת, כדאי להשתמש בספריות תבניות בטוחות עם בריחה אוטומטית לפי הקשר, יחד עם מדיניות אבטחת תוכן שמבוססת על קוד חד-פעמי כדי לצמצם את מספר הבאגים.
עכשיו הדפדפנים יכולים גם למנוע פרצות XSS מבוססות-DOM בצד הלקוח באמצעות סוגי נתונים מהימנים.
מבוא ל-API
התכונה Trusted Types פועלת על ידי נעילת הפונקציות הבאות של יציאת נתונים (sink) עם סיכון גבוה. יכול להיות שכבר שמעתם על חלק מהן, כי ספקי הדפדפנים ומסגרות האתרים כבר ממליצים לא להשתמש בתכונות האלה מסיבות אבטחה.
- מניפולציה בסקריפטים:
<script src>
והגדרת תוכן הטקסט של רכיבי<script>
. - יצירת HTML ממחרונית:
- הפעלת תוכן הפלאגין:
- הדרכה על הידור קוד JavaScript בסביבת זמן ריצה:
eval
setTimeout
setInterval
new Function()
כשמשתמשים ב-Trusted Types, צריך לעבד את הנתונים לפני שמעבירים אותם לפונקציות sink האלה. שימוש רק במחרוזת נכשל כי הדפדפן לא יודע אם הנתונים מהימנים:
anElement.innerHTML = location.href;
כדי לציין שהנתונים עברו עיבוד מאובטח, יוצרים אובייקט מיוחד – סוג מהימן.
anElement.innerHTML = aTrustedHTML;
השימוש בסוגים מהימנים מפחית באופן משמעותי את שטח ההתקפה של האפליקציה למתקפות XSS ב-DOM. הוא מפשט את בדיקות האבטחה ומאפשר לאכוף את בדיקות האבטחה שמבוססות על סוגים, שמבוצעות במהלך הידור, איתור שגיאות בקוד או קיבוץ הקוד בזמן הריצה בדפדפן.
איך משתמשים ב-Trusted Types
הכנה לקראת דוחות על הפרות של מדיניות האבטחה של תוכן
אפשר לפרוס אוסף דוחות, כמו 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:
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 ולהסיר ממנו עומסי עבודה של XSS.
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});
DOMPurify תומך בסוגי Trusted ומחזיר HTML מנוקה עטוף באובייקט TrustedHTML
כדי שהדפדפן לא ייצור הפרה.
יצירת מדיניות של סוג מהימן
לפעמים אי אפשר להסיר את הקוד שגורם להפרה, ואין ספרייה לניקוי הערך וליצור סוג מהימן. במקרים כאלה, תוכלו ליצור אובייקט מסוג Trusted Type בעצמכם.
קודם כל, יוצרים מדיניות. כללי המדיניות הם מפעלים של סוגים מהימנים שמפעילים כללי אבטחה מסוימים על הקלט שלהם:
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
createHTML: string => string.replace(/\</g, '<')
});
}
הקוד הזה יוצר מדיניות בשם myEscapePolicy
שיכולה ליצור אובייקטים מסוג TrustedHTML
באמצעות הפונקציה createHTML()
שלה. הכללים שהוגדרו מבצעים בריחה מ-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 שמקבל רק את הסוג Trusted.
מעבר לאכיפת Content Security Policy
כשהאפליקציה לא תיצור יותר הפרות, תוכלו להתחיל לאכוף את סוגי הפריטים המהימנים:
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
עכשיו, לא משנה כמה מורכבת אפליקציית האינטרנט שלכם, הדבר היחיד שיכול להציג נקודת חולשה של DOM XSS הוא הקוד באחד מהכללי המדיניות שלכם, ואפשר לנעול אותו עוד יותר על ידי הגבלת יצירת המדיניות.