על התקפת XSS – עם הדגמה על אתר בחדרי חרדים

בפוסט זה נסביר מה היא פרצת XSS – Cross Site Scripting ונראה הדגמה לחולשה הזו באתר פורומים אמיתי. החולשה מאפשרת לכל משתמש בפורום להזריק קוד JavaScript בדפדפן של משתמש אחר ולהריץ אותו, מה שמאפשר אפילו להתחבר בחשבון של המשתמש האחר. נראה איך החולשה הזאת קיימת כתוצאה מחוסר שימוש בספריות מעודכנות לאבטחת הקוד ואיך אפשר בקלות יחסית להתגבר עליה.

מהי התקפת XSS

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

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

למה זה בעייתי?

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

פרצת XSS יכולה להיות בכל שפה, למשל פרצת log4shell הידועה. אנחנו נתמקד בפרצות שקורות בקוד JavaScript באתרי אינטרנט.

איך מתרחשת התקפת XSS בדפדפן?

לרוב פרצת XSS תקרה על ידי הכנסת תוכן גולשים לאתר. נחשוב על אתר שמציג פרטים של משתמש אחר. נניח שהוא עושה את זה בדרך הבאה: document.querySelector(“#username”).innerHTML = user.username. משתמש שיבין כי האתר מציג בצורה כזאת את השם שלו, יוכל להכניס את שם המשתמש המתוחכם והזדוני David <script>console.log(document.cookie) </script> Cohen אם האתר לא יהיה מודע לכך ויקבל את שם המשתמש כמות שהוא, הרי שכאשר הוא ינסה להציג אותו כאשר ניכנס לעמוד, הקוד שהוחדר יתבצע והעוגייה שלנו תודפס לקונסול. הדוגמא הזאת היא בלתי מזיקה, אבל קוד אחר יכול לשלוח את ה-cookie לשרת חיצוני או לעשות דברים זדוניים אחרים, כפי שתיארנו לעיל.

ההתקפה הפשוטה והנאיבית הזו ניתנת לפתרון בקלות: במקום להכניס את שם המשתמש באמצעות innerHTML יש להשתמש ב-innerText בצורה הזאת: document.querySelector(“#username”).innerText = user.username. בדרך זו הדפדפן מתייחס תמיד לשם המשתמש כטקסט ולא כקוד. וכך, שם המשתמש שיוצג הוא "David <script>…</script> Cohen" והדפדפן לא יתייחס אליו כקטע קוד ולא יריץ אותו. גם כאשר הדף נוצר בצד השרת יש דרכים דומות למנוע מהדפדפן לפרש את הטקסט הזה כקטע קוד, אך לצורך הבנת העיקרון די לנו בדוגמא של צד הקליינט.

תוכן גולשים עשיר

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

כדי שהפיצ׳ר הזה יעבוד כראוי, הדף חייב לפרש את הטקסט המוזן בתור אלמנטים של HTML ולא כטקסט פשוט, ולכן הקוד: <div>This is <b>camelCase</b></div> יגרום שהמילים "camelCase" יודגשו.

אולם, כעת המשתמשים יכולים גם להכניס סקריפטים לתוך ההודעה, למשל:
<div>This is camelCase<script>....</script></div>
במקרה הזה הסקריפט שיש באמצע ההודעה ירוץ אצל כל המשתמשים שיקראו את ההודעה. למרות שיוצרי האתר לא יודעים שהוא קיים, הדפדפן חושב שזה סקריפט שהגיע מהאתר עצמו ולכן לגיטימי להריץ אותו. בתור בעלי האתר אנו חייבים למצוא דרך למנוע מכותבי ההודעה להריץ קוד JavaScript באתר, למרות שאנו מאפשרים להם להכניס HTML ״מתוחכם״ ולא רק טקסט פשוט.

מה עושים

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

איך לא לעשות סניטיזציה

הדרך הכי פשוטה לפתור את הבעיה היא לסנן תגיות מסוג script: כאשר נראה את המחרוזת <script> באמצע הטקסט פשוט נמחק אותו וכך נבטיח שהטקסט שיוכנס לעמוד שלנו לא יכיל אלמנטים של קוד. הבעיה היא שאפשר להגדיר קוד JavaScript גם ללא שימוש בתגיות script. למשל, אפשר להגדיר לכל אלמנט מאפיין onclick ולגרום לקוד שבתוכו לרוץ במקרה שהמשתמש יקיש עליו. הנה דוגמה לקוד כזה.
<div onclick="console.log(document.cookie)"></div>

גישה בעייתית נוספת היא לאפשר להריץ סקריפט אבל לנסות לשלוט מה הסקריפט מורשה לעשות ומה לא. למשל, במערכת הפורומים של בחדרי חרדים נהגו להחליף כל מקום שכותבים בהודעה document.cookie בכוכביות. הבעיה בגישה זו היא שמאוד קל ליצור קוד שעושה מה שאנחנו רוצים בלי לכתוב את המילים "האסורות". למשל במקום לכתוב document.cookie נכתוב document["coo" + "kie"], כך שמנוע החוקים של האתר כנראה לא יצליח להבין שמדובר בקוד בעייתי.

דוגמה למתקפת xss – בחדרי חרדים

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

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


<img
id="xss-image-2"
src="/"
onerror="console.log(document.cookie);
document.querySelector('#xss-image-2').src = 'https://upload.wikimedia.org.wikipedia/commons/c/ca/1×1.png"
/>
<script>console.log("message")</script>

אחרי הסניטיזציה של האתר הקוד נראה כך:


<img
id="xss-image-2"
src="/"
onerror="console.log(***************);
document.queryor('#xss-image-2').src = '<a href=" https:="" upload.wikimedia.org.wikipedia="" commons="" c="" ca="" 1×1.png';"="" target="_blank"
>

נשים לב שהתג script נעלם לגמרי, ה-document.query הוחלף בכוכביות, והמילה querySelector הוחלפה ב-queryor. בנוסף לשינויים הנובעים מסניטיזציה, הלינק לויקיפדיה הוחלף בתג a עם href, כנראה מתוך רצון להציג קישורים בצורה אחידה.

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


<img
id="xss-image"
src="/"
onerror="d = document;
c = ('cooki' + 'e').trim();
qs = 'queryS' + 'elector';
console.log(d[c]);
d[qs + 'All']('.top_nlsitem').forEach(n => n.style.backgroundColor = 'green');
d[qs]('#xss-image').src = 'ht' + 'tps://upload.wikimedia.org/wikipedia/commons/c/ca/1×1.png';"
/>

view raw

bhol-xss.html

hosted with ❤ by GitHub

בקוד הזה שמנו אלמנט img עם src לא תקין, מה שגורם לקריאה לפונקציה onerror שהגדרנו. בפונקציה הזאת אנחנו מדפיסים את ה-cookie ל-console. כאמור, פעולה זו אינה בעייתית כשלעצמה אך מהווה דוגמא למה שגורם זדוני היה יכול לעשות – לשלוח לעצמו את הערך של ה-cookie שאמור להישאר חסוי. על מנת להתגבר על הסניטיזציה שלהם אנחנו נאלצים לא לכתוב במפורש את המילה cookie ועושים מניפולציה קטנה על מחרוזות כדי ליצור אותה. לצורך ההדגמה הוספנו גם קוד שמוסיף רקע ירוק לכל שמות המשתמשים בדף. גם כאן אנחנו עושים מניפולציה קטנה בשביל לעקוף את מנוע החוקים של האתר ולהשתמש ב-querySelector למרות שהסניטיזציה מנסה לחסום את השימוש בו. אחר כך אנחנו מגדירים src תקין לתמונה כדי שהמשתמשים לא יבחינו בתמונה לא תקינה.

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

בחדרי פרצת xss

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

איך *כן* מתגברים על XSS


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


const sanitizeHtml = require("sanitize-html")
const evilHtml = `
<img
id="xss-image"
src="/"
onerror="d = document;
c = ('cooki' + 'e').trim();
qs = 'queryS' + 'elector';
console.log(d[c]);
d[qs + 'All']('.top_nlsitem').forEach(n => n.style.backgroundColor = 'green');
d[qs]('#xss-image').src = 'ht' + 'tps://upload.wikimedia.org/wikipedia/commons/c/ca/1×1.png';"
/>
`
const legitHtml = sanitizeHtml(evilHtml, { allowedTags: "img" })
console.log(legitHtml)
/* Output:
"\n<img src=\"/\" />\n"
*/

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

תודה לרן בר זיק על העזרה בכתיבת המאמר.

9 תגובות בנושא “על התקפת XSS – עם הדגמה על אתר בחדרי חרדים

הוסיפו את שלכם

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

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

    1. מאמר נהדר, מקיף ו"בגובה העיניים" למרות שאינכם אנשי אבטחת מידע "אמיתיים" 😉
      כל הכבוד, תודה רבה.

  3. השאלה אם בימינו הרשת הולכת ומצטמצמת לשימוש בכמה כלים ספציפיים ב-open source, למשל phpBB לפורומים ו-WordPress לאתרים רגילים, ושם יש מתכנתים שחושבים מראש על הדברים האלה, לעומת כלים In house.

  4. [אמנם יש עוד הרבה המלצות בתחום – אבל אני חושב שכדאי להוסיף עוד המצלה בסיסית וחשובה ביותר]

    בנוסף למה שאמרתי קודם, מומלץ ממש להגדיר את הקוקיז כ- HttpOnly, כך שלא יהיה ניתן לגשת אליהם דרך JS בכל מקרה.

נשמח לשמוע מה אתם חושבים על המאמר

ערכת עיצוב: Baskerville 2 של Anders Noren.

למעלה ↑

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

נרשמת בהצלח. בתודה, הגמל.

שגיאה בלתי צפויה, אנא נסה שוב.

camelCase will use the information you provide on this form to be in touch with you and to provide updates and marketing.