לדלג לתוכן

מוטציית XSS - mXSS מוסבר - כשהדפדפן עצמו הופך תוכן מסונן למסוכן

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

התהליך שיוצר את המוטציה

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

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

דוגמאות למנגנון, לא למטען מוכן

כדי להבין את זה בלי לספק payload מוכן לשימוש, שווה להסתכל על סוג התופעה:

  • תגיות עם כללי פירוש מיוחדים. תגיות כמו noscript, svg, או mglyph נמצאות בקטגוריה מיוחדת מבחינת מנתח ה-HTML - התוכן שבתוכן מתפרש לפי כללים שונים מהרגיל, תלוי בהקשר שבו הן מופיעות. סניטייזר שבודק את התוכן לפי כללי הפירוש "הרגילים" עלול לפספס איך אותו תוכן ייראה כשהוא בפועל בתוך אחת מהתגיות המיוחדות האלה.
  • קינון שגורם לתיקון אוטומטי. מנתח ה-HTML של הדפדפן מתוכנן "לתקן" מבנים לא תקינים - למשל תגיות שנפתחות בתוך הקשר שאינו מאפשר אותן. תהליך התיקון הזה יכול להזיז תגיות ותכונות למקום אחר בעץ, ולפעמים המקום החדש הזה הוא הקשר שמריץ קוד.
  • עיגול כפול - Double Round-Trip. התרחיש הקלאסי הוא שאפליקציה מסננת מחרוזת, שומרת אותה, ואז בהמשך מציגה אותה שוב דרך innerHTML פעם נוספת - לדוגמה בעריכה חוזרת של תוכן שנשמר. כל מעבר כזה דרך פירוש וסריאליזציה מחדש הוא הזדמנות נוספת למוטציה, גם אם המעבר הראשון היה נקי לגמרי.

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

למה זה מטעה מנגנוני סניטציה נאיביים

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

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

איך מתגוננים נכון

ההגנה היעילה נגד mXSS לא מתחילה מרשימות שחורות טובות יותר, אלא משינוי הגישה כולה:

  • סמכו על ניתוח DOM אמיתי, לא על מחרוזות. סניטייזרים טובים לא בודקים טקסט גולמי, הם בונים עץ DOM אמיתי, מנקים אותו ברמת העץ, ורק אז מסריאליזים אותו - כלומר הם עובדים באותה שכבה שבה המוטציה קורית, ולא לפניה.
  • השתמשו בספריות סניטציה מתוחזקות ופעילות. כתיבת סניטייזר משלכם היא כמעט תמיד טעות - ספריות בשלות עוברות שנים של מציאת מקרי קצה בדיוק כמו mXSS, ומתעדכנות כשמנועי דפדפן משנים התנהגות.
  • הימנעו מעיגולים מיותרים. כל פעם שתוכן עובר שוב דרך innerHTML אחרי שכבר עבר סניטציה, זו הזדמנות נוספת למוטציה. עדיף לשמור את הגרסה המקורית והנקייה ולא לבנות מחדש מתוך HTML שכבר נוצר.

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

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

הצטרפו לקהילה בדיסקורד

לסיכום

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