מניעת נקודות חולשה של סקריפטים חוצי-אתרים המבוססים על DOM באמצעות סוגים מהימנים

Krzysztof Kotowicz
Krzysztof Kotowicz

תמיכה בדפדפן

  • 83
  • 83
  • x
  • x

מקור

סקריפטים חוצי-אתרים (DOM XSS) שמבוססים על DOM מתרחשים כשנתונים ממקור בשליטת המשתמש (כמו שם משתמש או כתובת URL להפניה אוטומטית שנלקחה מקטע כתובת ה-URL) מגיעים ל-sink, שהוא פונקציה כמו eval() או מערך מאפיינים כמו .innerHTML, שיכול להפעיל קוד JavaScript שרירותי.

DOM XSS הוא אחת מנקודות החולשה הנפוצות ביותר באבטחת אינטרנט, וצוותי פיתוח נוטים להציג אותו בטעות באפליקציות שלהם. באמצעות סוגים מהימנים מקבלים את הכלים לכתוב, לבצע בדיקות אבטחה ולשמור על אפליקציות ללא נקודות חולשה ב-DOM XSS, על ידי הפיכת פונקציות מסוכנות של ה-API באינטרנט למאובטחות כברירת מחדל. הסוגים המהימנים זמינים כ-polyfill לדפדפנים שעדיין לא תומכים בהם.

רקע

במשך שנים רבות, DOM XSS הוא אחד מנקודות החולשה הנפוצות והמסוכנות ביותר באבטחת אינטרנט.

יש שני סוגים של סקריפטים חוצי-אתרים. נקודות חולשה מסוימות ב-XSS נגרמות על ידי קוד בצד השרת שיוצר באופן לא מאובטח את קוד ה-HTML שיוצר את האתר. לאחרים יש שורש הבעיה בלקוח, שבו קוד ה-JavaScript קורא לפונקציות מסוכנות עם תוכן שבשליטת המשתמש.

כדי למנוע XSS בצד השרת, אל תיצרו HTML על ידי שרשור מחרוזות. במקום זאת, כדאי להשתמש בספריות בטוחות של תבניות ליצירת תבניות אוטומטיות לפי הקשר, יחד עם מדיניות אבטחת תוכן המבוססת על חד-פעמיות, כדי לצמצם את הבאג.

מעכשיו, דפדפנים יכולים להשתמש בסוגים מהימנים כדי למנוע XSS שמבוסס על DOM בצד הלקוח.

מבוא ל-API

התכונה 'סוגים מהימנים' פועלת על ידי נעילה של פונקציות sink המסוכנות הבאות. יכול להיות שאתם כבר מזהים חלק מהם, כי ספקי דפדפנים ומסגרות אינטרנט כבר מונעים מכם את השימוש בתכונות האלה מטעמי אבטחה.

כדי להעביר את הנתונים לפונקציות ה-sink האלה צריך לעבד אותם. השימוש במחרוזת בלבד נכשל, כי הדפדפן לא יודע אם הנתונים מהימנים:

מה אסור לעשות
anElement.innerHTML  = location.href;
כשהאפשרות 'סוגים מהימנים' מופעלת, הדפדפן יוצר הודעת TypeError ומונע שימוש ב-sink של DOM XSS עם מחרוזת.

כדי לציין שהנתונים עובדו באופן מאובטח, יוצרים אובייקט מיוחד - 'סוג מהימן'.

מה מותר לעשות
anElement.innerHTML = aTrustedHTML;
  
כשסוגים מהימנים מופעלים, הדפדפן מקבל אובייקט TrustedHTML עבור אובייקטים מסוג sink שמצפים לקטעי HTML. יש גם אובייקטים TrustedScript ו-TrustedScriptURL ל-sinks רגישים אחרים.

סוגים מהימנים מצמצמים באופן משמעותי את שטח ההתקפה של DOM XSS של האפליקציה. היא מפשטת את בדיקות האבטחה ומאפשרת לאכוף את בדיקות האבטחה מבוססות-הסוג שמתבצעות בזמן הידור, איתור שגיאות בקוד (linting), וקיבוץ הקוד בזמן הריצה בדפדפן.

איך משתמשים בסוגים מהימנים

הכנה לקראת דוחות על הפרות של המדיניות בנושא אבטחת תוכן

אפשר לפרוס אוסף דוחות, כמו go-csp-collector בקוד פתוח, או להשתמש באחד מהכלים המקבילים המסחריים. אפשר גם לנפות באגים שמפרות את המדיניות בדפדפן:

document.addEventListener('securitypolicyviolation',
    console.error.bind(console));

הוספת כותרת CSP לדיווח בלבד

הוסיפו את הכותרת הבאה של תגובת HTTP למסמכים שרוצים להעביר ל'סוגים מהימנים':

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

עכשיו כל ההפרות מדווחות ל-//my-csp-endpoint.example, אבל האתר ממשיך לפעול. בקטע הבא מוסבר איך פועלת //my-csp-endpoint.example.

זיהוי הפרות של סוגים מהימנים

מעכשיו, בכל פעם ש'סוגים מהימנים' יזהו הפרה, הדפדפן ישלח דוח ל-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 = '';

שימוש בספרייה

חלק מהספריות כבר יוצרות סוגים מהימנים שאפשר להעביר לפונקציות sink. לדוגמה, אפשר להשתמש ב-DOMPurify כדי לנקות קטע HTML, ולהסיר מטענים ייעודיים (payloads) של XSS.

import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});

DOMPurify תומך בסוגים מהימנים ומחזיר HTML במצב נקי שמוקף באובייקט TrustedHTML, כדי שהדפדפן לא ייצור הפרה.

יצירת מדיניות מסוג Trusted Type

לפעמים אי אפשר להסיר את הקוד שגרם להפרה, ואין ספרייה שיחטיאה של הערך ויצרו עבורכם'סוג מהימן'. במקרים כאלה, תוכלו ליצור אובייקט מסוג Trusted Type בעצמכם.

קודם כול, יוצרים מדיניות. כללי מדיניות הם מפעלים של סוגים מהימנים שאוכפים כללי אבטחה מסוימים על הקלט שלהם:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
    createHTML: string => string.replace(/\</g, '&lt;')
  });
}

הקוד הזה יוצר מדיניות בשם myEscapePolicy שיכולה ליצור אובייקטים מסוג TrustedHTML באמצעות הפונקציה createHTML(). הכללים המוגדרים כ-HTML-escape < תווים כדי למנוע יצירה של רכיבי HTML חדשים.

צריך להשתמש במדיניות כך:

const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML);  // true
el.innerHTML = escaped;  // '&lt;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

כשהאפליקציה לא יוצרת יותר הפרות, תוכלו להתחיל לאכוף סוגים מהימנים:

Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

עכשיו, לא משנה עד כמה אפליקציית האינטרנט שלכם מורכבת, הדבר היחיד שיכול ליצור נקודת חולשה ב-DOM XSS הוא הקוד באחת מכללי המדיניות שלכם, ותוכלו לנעול אותו עוד יותר על ידי הגבלת יצירת המדיניות.

קריאה נוספת