בחינת IPA SC, סתיו 2023 (Reiwa 5), שאלה 1 אחר-הצהריים — ה-Stored XSS שבו 16 ביקורות מוצגות כ-2
· עודכן בתאריך: · Go Komura · מומחה אבטחת מידע רשום, מומחה אבטחה רשום, XSS, Cross-Site Scripting, אפליקציית Web, אבטחת מידע, פגיעות, IPA, ניהול session
“אמורות להיות 16 ביקורות, ובפועל מוצגות רק 2.”
שאלת אחר-הצהריים 1 בבחינת IPA SC (Registered Information Security Specialist) של סתיו 2023 (Reiwa 5) מתחילה בפנייה הזו של משתמש.1 אין שום דבר מוזר במעברי המסכים, וגם לא מופיעה שגיאה. רק מספר הפריטים המוצגים לא מסתדר.
הסיבה הייתה Stored Cross-Site Scripting (XSS). אבל מה שמעניין בשאלה הזו הוא לא התשובה “XSS” עצמה. מה שמעניין הוא שכל אחד מהטיפולים ה”סבירים” שחברת Q (מפעילת אתר ה-e-commerce מהשאלה) כבר הפעילה — נעקף. הגבלת 50 התווים על כותרת הביקורת נשברה, ה-token שנדרש ל-upload הושג בדרך התקינה, וה-session ID שנגנב הוצא החוצה בלי להישלח אף פעם לשרת חיצוני.
המאמר הזה קורא את המקרה בסדר הזה: “מה קרה, אילו עקבות הן הבסיס לתשובה, ואיזה טיפול היה עוצר את זה”. מעבר לשם XSS, הוא מסדר היכן עובר הגבול בין הטיפולים שלא עבדו לבין אלה שהיו עובדים.
הוא מכסה את דוגמאות התשובה של הבחינה ואת הבסיס לכל אחת מהן, וגם תמונה שלמה של טיפולי XSS שאפשר להשתמש בהם בעבודה. בחרו את נקודת הכניסה שמתאימה למטרה שלכם.
| למה אתם קוראים | מה לפתוח ראשון | איך להמשיך |
|---|---|---|
| רוצים לפתור שוב את השאלה | טבלת ההתאמה של הסעיפים בפרק 2 | לפתוח את חוברת השאלות ולבדוק את דוגמאות התשובה והבסיס שלהן בפרקים 4 עד 8 |
| רוצים לדעת למה 16 ביקורות נראות כמו 2 | הסימפטום בפרק 3 | לתפוס את ההבדל בין מה שמוצג ל-HTML, ומשם לעבור לפרסום המפוצל בפרק 5 |
| רוצים לראות את כל הנתיב שבו המידע יוצא החוצה | תרשים ה-sequence בפרק 7 | לעקוב בנפרד אחרי הפרסום, אחרי ההרצה בצד הקורבן ואחרי האיסוף בצד התוקף |
| רוצים להשתמש בזה לטיפולים או לסקירה בעבודה | השוואת הטיפולים בפרק 9, נקודות הבדיקה בפרק 10 | להפריד בין הפתרון היסודי לאמצעי הגנה משניים, ולבדוק את הטווח שהטיפול מכסה ואת מגבלותיו |
בפרקים 4 עד 8 דוגמאות התשובה הן התשובות של הבחינה, ואילו CSP ו-re-encode של תמונות וכדומה הן השלמה שמרחיבה לעבודה המעשית. במיוחד בפרק 8 מפרידים בין התנאים שכתובים בשאלה לבין התנאים שחלים כשמשנים הגדרה.
לשאלה הרחבה יותר — באיזה קריטריון כדאי לבדוק את האבטחה של אתר שלם — יש התייחסות בעל מה שגם מזמיני אתרים צריכים לדעת ── להשתמש ב”איך לבנות אתר בטוח” של IPA כרשימת בדיקה. המאמר הזה מעמיק באחת מ-11 החולשות שמופיעות שם, XSS, עם דוגמה מעשית.
1. קודם כל, המסקנות
מה קרה
- סוג החולשה הוא Stored XSS. המחרוזת של התוקף נשמרה בשרת ומכאן והלאה הוצאה ל-HTML של כל מי שפתח את העמוד. האם ה-script המוטמע משתמש ב-DOM API, והאם מדובר ב-DOM Based XSS — אלה שני עניינים שונים
- הגבלת מספר התווים לא עבדה כטיפול ב-XSS. התוקף פיצל את ה-payload ל-15 פרסומים וגרם ל-HTML שנמצא בין הפרסומים להיות מדולג כהערה ב-JavaScript, וכך חיבר את הקטעים ל-script אחד
- ה-token ל-upload (טיפול CSRF) גם הוא לא עבד כטיפול ב-XSS. ה-script התוקף משיג את ה-token באותו הליך של המסך הלגיטימי. כל עוד הוא רץ על אותה origin, הוא יכול לעשות כל מה שמשתמש לגיטימי יכול לעשות
- ה-session ID שנגנב לא נשלח החוצה. תוכן ה-cookie הונח על פונקציית העלאת תמונות הפרופיל של האתר עצמו כקובץ תמונה בשם “a.png”, והתוקף אסף אותו בפשוט על ידי צפייה בו. טיפולי יציאה (egress) לא מזהים את זה
איפה זה היה יכול להיעצר
- השאלה מציינת במפורש שלושה דברים שחסרו לחברת Q: escaping בזמן הפלט (הפתרון היסודי), והתכונה HttpOnly על ה-cookie ובדיקת הפורמט של קבצים שמועלים (שני אלה אמצעי הגנה משניים). כל אחד מהם היה שובר את שרשרת התקיפה הזו. בדיקת הפורמט לבדה, לעומת זאת, יכולה להיעקף כשמשנים שיטה, ולכן דרוש גם re-encode
script-srcשל CSP, למרות שהוא לא מופיע בשאלה, גם הוא יכול לשבור את אותה שרשרת. בהגדרה שלא מתירה'unsafe-inline', ה-script inline שהוטבע לא רץ מלכתחילה
“עובד” פה פירושו להיות מסוגל לעצור את שרשרת התקיפה של השאלה הזו. HttpOnly או re-encode של תמונה לבדם לא מבטלים את ה-XSS עצמו. בפרק 9 נבדוק גם את המגבלות שמתגלות כשהשיטה משתנה.
ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 25, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle
2. על מקור השאלה ── הייחוס ואיך המאמר מטפל בו
השאלה שנדונה כאן היא זו.
מקור: מבחן Registered Information Security Specialist, סתיו 2023 (Reiwa 5), אחר-הצהריים, שאלה 1
IPA מציינת שלגבי שאלות בחינה מן העבר שהיא מפרסמת, אין צורך באישור או בתשלום, למעט מקרים שבהם החוק קובע אחרת. זה לא אומר שוויתרו על זכויות היוצרים: היא דורשת לציין את המקור בצורה “שנת מס, מחזור, סוג הבחינה, מקטע הזמן, מספר השאלה וכדומה”, ולדאוג לציין גם כששונו חלקים מהשאלה.2
המאמר הזה לא מעתיק כלשונם את ה-HTML או את ה-scripts שמופיעים בחוברת השאלות. במקום שנדרש להסביר את המנגנון, הם מוחלפים בקוד לדוגמה שקול שאנחנו כתבנו. גם נוסח השאלות וגם דוגמאות התשובה מובאים כאן בתמצית. את חוברת השאלות המקורית, את דוגמאות התשובה ואת הערות הציון אפשר להוריד בחינם מהעמוד של IPA, ואנחנו ממליצים לקרוא את המאמר כשהם פתוחים לידכם.1 3 4
ההתאמה בין הסעיפים למאמר
למי שקורא עם חוברת השאלות פתוחה, הנה ההתאמה בין הסעיפים לבין הפרקים במאמר. אפשר להתחיל מהסעיף שרוצים לפתור.
| סעיף | מה נשאל (מספר תווים) | הפרק המתאים במאמר |
|---|---|---|
| סעיף 1(1) | סוג חולשת ה-XSS שנוצלה (3 אפשרויות) | פרק 4 |
| סעיף 1(2) | הטיפול ב-Web app Q (עד 30 תווים) | פרק 4, “שאלה 1(2): הטיפול” |
| סעיף 2 | הדרך שבה הורץ script ארוך מהגבלת מספר התווים (עד 50 תווים) | פרק 5 |
| סעיף 3(1) | מה עושות שורות 6 עד 20 של ה-script התוקף (עד 60 תווים) | פרק 6 |
| סעיף 3(2) | איך התוקף משיג את המידע שהועלה (עד 50 תווים) | פרק 7 |
| סעיף 3(3) | מה אפשר לעשות עם המידע שהושג (עד 40 תווים) | פרק 7, “שאלה 3(3): מה אפשר לעשות עם ה-session ID” |
| סעיף 4 | המנגנון בדפדפן שבגללו התקיפה לא מצליחה בדומיין של התוקף (עד 40 תווים) | פרק 8 |
מי שרוצה רק את הצד המעשי ולא את הסעיפים, יכול לקרוא מפרק 9 (רשימת הטיפולים שעבדו וטיפולים שלא עבדו) ופרק 10 (נקודות לסקירת קוד). מי שרוצה לראות את שרשרת התקיפה כולה בעמוד אחד, ימצא תרשים sequence בפרק 7 שמציג את כל התמונה מהפרסום ועד האיסוף.
ההתאמה בין מה שכתוב בחוברת השאלות לבין הדוגמאות במאמר
כדי שתוכלו להשוות לטקסט המקורי, הנה תמצית של מה הוחלף ואיך.
| מה שכתוב בחוברת השאלות | איך זה מטופל במאמר | היכן זה מופיע |
|---|---|---|
| ה-HTML של עמוד V (כולל כותרות הביקורת שפורסמו במפוצל) | לא מועתק כלשונו; אנחנו כתבנו דוגמה שקולה שמכווצת לאותם מנגנונים בשלושה פרסומים | פרק 5 |
| ה-script התוקף שחולץ (כ-20 שורות) | לא מועתק כלשונו; כתבנו JavaScript שקול שעושה את אותה עבודה. ההערות בקוד הן בשביל ההסבר במאמר הזה | פרק 6 |
| הנוסח של כל סעיף | תמצית ששומרת על התוכן (תנאים כמו הגבלות מספר תווים מובאים בערכים המקוריים) | בראש כל אחד מהפרקים 4 עד 8 |
| דוגמאות התשובה | דוגמאות התשובה ש-IPA מפרסמת3 | לאורך הפרקים 4 עד 8 |
| הערות הציון | הקטעים הרלוונטיים מתוך הערות הציון ש-IPA מפרסמת4 | פרקים 4, 5 ו-7 |
| המפרט בשאלה (חברת Q, עמוד V, חברים A ו-B, הפונקציות והגבלות מספר התווים וכדומה) | תמצית לפי לשון השאלה | פרק 2, “זירת השאלה” |
זירת השאלה
החברה שמדובר בה היא חברת Q, עסק e-commerce לבגדים עם 100 עובדים. היא מפעילה חנות מקוונת על “Web app Q” שפותחה בבית, ומשתמשים ניגשים אליה ב-HTTPS. ההנחה היא שהיא בדיוק הוסיפה פונקציית ביקורות מוצר לחברים.
יש חמישה פרטי מפרט שחשוב לזכור.
| פונקציה | מפרט |
|---|---|
| התחברות | אימות לפי מזהה חבר וסיסמה, והנפקת session ID כ-cookie |
| ביקורת מוצר | רק חברים מחוברים יכולים לפרסם. לכותרת הביקורת יש הגבלת קלט של 50 תווים ולתוכן הביקורת 300 תווים, ושניהם טקסט חופשי |
| פרופיל חבר | מספק עמוד להעלאת תמונת פרופיל ועמוד לרישום פרטי כרטיס אשראי. שניהם זמינים רק לחברים מחוברים |
| העלאת תמונת פרופיל | שולח לקובץ התמונה ולtoken כפרמטרים אל /user/upload. ההעלאה מצליחה רק כשה-token תואם לזה שהונפק ב-/user/profile |
| הצגת תמונת הפרופיל | תמונת הפרופיל שמועלת מוצגת בעמוד הגדרות הפרופיל של החבר ובעמודי הביקורות |
שתי השורות האחרונות הן הרמז לחשוב על “מה צריך כדי להעלות” ועל “מי יכול לקרוא את מה שהועלה”. השגת ה-token נבדקת בפרק 6, והאיסוף דרך התמונה בפרק 7.
3. הסימפטום ── למה 16 הפכו ל-2
קודם משווים בין “המספר במסך” ל”מספר ב-HTML”
מגיעה פנייה מחבר: בעמוד הביקורות של חולצת ה-T הרגילה (עמוד V) מוצגות רק 2 מתוך 16 הביקורות שאמורות להופיע. כשמר N ממחלקת הפיתוח פותח את עמוד V, המסך נראה כך.
- בכותרת כתוב “16 ביקורות”
- ביקורת אחת של חבר A (כותרת “Good”, טקסט “Nice shirt!”)
- ביקורת אחת של חבר B
- בסוף כתוב “אלה כל 16 הביקורות”
המספר שמוצג הוא 16, אבל בפועל מונחים שם 2 בלבד. כשמסתכלים על ה-HTML בשלב הזה מגלים שיש 15 פרסומים של חבר A, ובתוכם מוטמע script ארוך.
כלומר, כל 16 הביקורות אכן מופיעות ב-HTML. הסיבה שרואים רק 2 היא שרובן הפכו לתוכן של רכיב <script>, ולכן הדפדפן הפסיק להתייחס אליהן כתוכן שיש להציג.
מה שנשאר מוצג: החצי הראשון של הפרסום ה-1 והחצי השני של ה-15
הטווח שנבלע הוא, ליתר דיוק, לא “כל 15 הפרסומים”. תג הפתיחה <script> מופיע באמצע כותרת הביקורת הראשונה (מיד אחרי “Good”), ותג הסגירה </script> נמצא בסוף כותרת הביקורת ה-15. ולכן:
- הכרטיס של הפרסום הראשון (תמונת פרופיל, שם תצוגה, תאריך, כוכבים, וכותרת עד “Good”) נמצא לפני
<script>, ולכן מוצג - מאותה נקודה ועד סוף הכותרת ה-15 — הכול נבלע כתוכן של רכיב ה-script
- שאר הפרסום ה-15 (טקסט הביקורת “Nice shirt!”) נמצא אחרי
</script>, ולכן מוצג
התוצאה היא שהחלק העליון של הפרסום הראשון מתחבר לטקסט של הפרסום ה-15, ובמסך זה נראה כמו “ביקורת אחת של חבר A, כותרת Good וטקסט Nice shirt!”. מוסיפים לזה את הביקורת האחת של חבר B ומקבלים 2. התוקף כנראה שם בכוונה מחרוזות שנראות טבעיות — “Good” בראש הפרסום הראשון ו-“Nice shirt!” בטקסט של ה-15 — כדי שהפריסה השבורה לא תיראה חריגה.
היכולת לקרוא את הסימפטום הזה — “המספר נכון אבל התצוגה לא” — שימושית גם בעבודה בפועל. היא נקודת הכניסה לחשד שמדובר לא בבאג בלוגיקת ספירת הפריטים, אלא במבנה ה-HTML שיצא החוצה והוא זה שנשבר.
4. למה זה XSS “Stored” ── סעיף 1
סעיף 1(1): התשובה, והעקבות שמשמשים להכרעה
בסעיף 1 מבקשים לבחור את סוג חולשת ה-XSS שנוצלה בתקיפה הזו מתוך שלוש אפשרויות: DOM Based XSS, Stored XSS ו-Reflected XSS. התשובה הנכונה היא Stored XSS.3
הבסיס הוא שהמחרוזת שפורסמה נשמרה בשרת ויצאה ל-HTML כמו שהיא. אל תבחרו את הסוג לפי שמות ה-API שה-script משתמש בהם אחרי שהוא רץ.
ולגבי הסעיף הזה הערות הציון של IPA כותבות כך.4
שיעור התשובות הנכונות היה ממוצע, אבל נראו נבחנים אחדים שענו בטעות “DOM Based XSS”, כנראה מפני שה-script השתמש ב-DOM.
ה-script התוקף שמופיע בשאלה משתמש ב-XMLHttpRequest, מקבל את ה-response כ-DOM, ומאתר רכיב עם getElementById. הוא באמת נוגע ב-DOM. אבל אין לזה שום קשר לסוג החולשה.
קו הגבול הוא היכן שמחרוזת התקיפה הופכת ל-script
מה שמפריד בין שלוש הקטגוריות הוא היכן מחרוזת התקיפה הופכת לקוד שאפשר להריץ, כלומר האם נקודת הפלט הפגיעה (ה-sink) נמצאת בצד השרת או בצד הדפדפן.
| סוג | ה-sink (היכן שמחרוזת התקיפה הופכת לניתנת להרצה) | מאיפה מגיעה מחרוזת התקיפה |
|---|---|---|
| Reflected XSS | עיבוד בניית ה-HTML של השרת | הבקשה עצמה, למשל פרמטר ב-URL שהתוקף הכין |
| Stored XSS | עיבוד בניית ה-HTML של השרת | נתונים שנשמרו בצד השרת |
| DOM Based XSS | JavaScript בדפדפן (השמה ל-innerHTML, eval, document.write וכדומה) |
fragment ב-URL, postMessage, ערך שהתקבל מהשרת וכדומה |
בשאלה הזו השרת הטמיע את כותרת הביקורת שפרסם התוקף ישירות לתוך ה-HTML והחזיר אותה. ה-sink נמצא בעיבוד הפלט בצד השרת, והמחרוזת נשמרת במסד הנתונים ומכאן והלאה נמסרת לכל מי שפותח את עמוד V. ולכן זה Stored XSS.
flowchart TD
A["המחרוזת של התוקף רצה<br/>כ-script"] --> B{"היכן היא הפכה<br/>לניתנת להרצה"}
B -->|"כש-JS בדפדפן העביר אותה<br/>ל-innerHTML<br/>או למשהו דומה"| C["DOM Based XSS"]
B -->|"היא הייתה בתוך ה-HTML<br/>שהשרת בנה"| D{"האם המחרוזת הזו<br/>נשמרת בשרת"}
D -->|"נשמרת<br/>(מכאן והלאה מוצגת לכולם)"| E["Stored XSS"]
D -->|"לא נשמרת<br/>(רק לבקשה הזו)"| F["Reflected XSS"]
פשוט יתר של הדבר ל”אם מחרוזת התקיפה נמצאת ב-response של השרת, זה Stored או Reflected” הוא מסוכן. גם כשהשרת מחזיר את הערך כטקסט תמים או כ-JSON, זה הופך ל-DOM Based XSS ברגע ש-JavaScript בדפדפן משמה אותו ל-innerHTML (זה מה שמכונה stored DOM XSS, שבו ערך שמור הוא הגורם). במקרה כזה מה שצריך לתקן הוא לא עיבוד הפלט של השרת אלא ה-sink בצד הלקוח, ולכן שפטו לפי היכן הערך הפך לניתן להרצה, ולא לפי האם המחרוזת נראית ב-response.
את מה שה-script המוטמע עושה (נוגע ב-DOM, מתקשר, קורא cookies) ואת איך ה-script הזה נכנס לעמוד צריך לחשוב בנפרד. בלבול בין השניים מפריע לא מפני שקשה לבחור טיפול, אלא מפני שהוא גורם לבחור טיפול שגוי. קביעה שמדובר ב-DOM Based XSS מובילה למסקנה ש”צריך לתקן את ה-JavaScript בצד הלקוח”, בעוד שמה שבאמת היה צריך תיקון הוא עיבוד הפלט בצד השרת.
סעיף 1(2): הטיפול
בסעיף 1(2) מבקשים לומר את הטיפול ב-Web app Q בעד 30 תווים.
דוגמת התשובה: “לבצע escaping על כותרת הביקורת לפני הפלט”.3
הבסיס לתשובה: המקום לתקן הוא לא המסך שמקבל את הפרסום, אלא העיבוד שמטמיע את כותרת הביקורת השמורה לתוך ה-HTML.
העובדה שהסדר — “לפני הפלט” — כתוב במפורש כאן היא חשובה. לא עושים escaping כשמקבלים את הקלט, אלא ממש לפני הפלט כ-HTML, לפי ההקשר של היעד. גם “איך לבנות אתר בטוח” של IPA מציבה את “לבצע escaping על כל רכיב שמוצג לעמוד ה-web” בראש הפתרונות היסודיים ל-XSS.5
“בהזרקת SQL escaping של הקלט הוא העיקר, לא?”
מכאן והלאה זו השלמה שמרחיבה את “לפני הפלט” של סעיף 1(2) אל העבודה המעשית. קראו אותה בנפרד מדוגמת התשובה עצמה.
קודם כל המסקנה: גם בהזרקת SQL, escaping בזמן הקלט אינו הטיפול.
מה ש-IPA מציגה כפתרון היסודי להזרקת SQL הוא “לממש את כל בניית משפטי ה-SQL עם placeholders”. escaping מופיע כחלופה למקרה שבו חייבים לבנות משפט SQL בשרשור מחרוזות, אבל גם שם הנוסח הוא “כשמרכיבים משפט SQL בשרשור מחרוזות, יש להשתמש ב-API של מנוע מסד הנתונים שמבצע escaping וכדומה, כדי לבנות נכון את ה-literals של משפט ה-SQL” — כלומר ה-escaping נעשה ברגע שבו מרכיבים את משפט ה-SQL.6 לא ברגע שבו מקבלים את הקלט.
המבנה הוא אפוא בדיוק כמו ב-HTML. העיקרון המשותף יוצא כך.
escaping נעשה במקום שבו נקבע לאיזו שפה דקדוקית הנתונים יוצאים.
- אם יוצא כ-HTML — escaping של HTML
- אם נעשה חלק ממשפט SQL — placeholders (או, אם אין ברירה, escaping כ-literal של SQL)
- אם נעשה חלק מפקודת shell — עיבוד לפי הכללים של ה-shell
לא בזמן הקלט, אלא במקום שבו נקבע היעד של הפלט
למה לא בזמן הקלט? מפני שברגע שבו מקבלים את הקלט, עוד לא נקבע לאן הנתונים ייצאו. אותו טקסט של ביקורת יוצא גם לעמוד HTML, גם לייצוא CSV, גם ל-response של JSON ב-API, גם למייל התראה וגם ל-log. אם עושים escaping של HTML בזמן הקלט, המחרוזת & תופיע ב-CSV כמו שהיא, ומסד הנתונים יחזיק משהו שונה מהקלט המקורי, וכך גם החיפוש וגם הצבירה ייצאו מעוותים. זה גם קרקע להכפלת escaping.
אז בצד הקלט לא צריך לעשות כלום?
לא. מה שהצד של הקלט עושה הוא validation ולא escaping. התפקידים שונים.
- לדחות בקלט — לא לקבל ערכים שאינם אפשריים לפי המפרט. שדה מיקוד יסרב לכל דבר חוץ מ-7 ספרות, ושדה כמות יסרב למספרים שליליים. זה עיבוד שנדרש בפני עצמו, כדי לשמור על נכונות הנתונים
- לעשות escaping בפלט — להציג את הנתונים שהתקבלו בצורה בטוחה לפי הדקדוק של היעד שלהם. זה הפתרון היסודי לחולשה
וחשוב גם לא לצפות יותר מדי מ-validation בצד הקלט כטיפול ב-XSS. לגבי XSS, IPA נוגעת בשיטה של לבדוק אם ערכי הקלט תואמים את המפרט של האפליקציה, ואז אומרת במפורש שהיעילות של הטיפול הזה מוגבלת, שבמקרה שבו המפרט של האפליקציה מתיר קלט במגוון רחב של סוגי תווים הוא בכלל לא טיפול, ולכן לא מומלץ להסתמך על השיטה הזו.5
כותרת הביקורת וטקסט הביקורת כאן היו בדיוק כאלה: שדות טקסט חופשי שהמפרט שלהם מתיר מגוון רחב של סוגי תווים. יש גבולות עליונים של 50 ו-300 תווים, אבל מספר התווים שנדרש כדי להריץ script קטן בהרבה מזה, ולכן עצם קיומו של גבול עליון אינו הגנה. מה שבאמת קרה בחברת Q הוא מה שסעיף 2, שמגיע מיד, בודק.
5. איך נשברה הגבלת 50 התווים ── סעיף 2
סעיף 2 הוא נקודת השיא של שאלה זו.
לגבי איור 3, תארו בעד 50 תווים את הדרך שבה הורץ script ארוך מהגבלת מספר התווים של הקלט.
דוגמת התשובה: “התוקף פרסם את התוכן בכמה פרסומים מפוצלים, כך שה-HTML הפך להערה והכול התחבר ל-script אחד”.3
התשובה חייבת לכלול לא רק את הפרסומים המרובים אלא גם את המנגנון שמייצר דילוג על ה-HTML שבאמצע כהערה. נבדוק אותה בשלושה שלבים.
להסתכל לפי הסדר: הפרסום, ה-HTML וה-JavaScript
התוקף פיצל את ה-payload לאורכים שנכנסים בפרסום בודד ופרסם 15 פעמים. הנקודה המרכזית היא מה עושים עם ה-HTML שחייב להיכנס בין פרסום לפרסום (</div>, <div class="..."> וכדומה). בתור JavaScript אלה שגיאות תחביר.
מה ששימש כדי להתמודד עם זה הוא הערת בלוק של JavaScript. הבא הוא גרסה מפושטת שאנחנו כתבנו כדי להסביר את המנגנון (זה לא ציטוט מדויק של איור מחוברת השאלות).
1. המחרוזות שמפורסמות
נניח ששדה כותרת הביקורת פורסם בשלושה חלקים, כך.
פרסום 1: נהדר<script>a=1;/*
פרסום 2: */b=2;/*
פרסום 3: */c=3;</script>
2. ה-HTML שהשרת בונה
השרת מוציא את אלה ככותרת של כל ביקורת, ומייצר HTML כך.
<div class="review-title">נהדר<script>a=1;/*</div>
<div class="description">…</div>
<div class="review-title">*/b=2;/*</div>
<div class="description">…</div>
<div class="review-title">*/c=3;</script></div>
3. ה-JavaScript שהדפדפן מריץ
מנקודת המבט של הדפדפן, הכול מהתגית <script> הראשונה ועד ה-</script> האחרונה הוא רכיב script אחד. התוכן שלו יוצא כך.
a=1;/*</div>
<div class="description">…</div>
<div class="review-title">*/b=2;/*</div>
<div class="description">…</div>
<div class="review-title">*/c=3;
ה-HTML שנמצא בין /* ל-*/ מדולג כהערת JavaScript, ומה שבאמת רץ הוא רק a=1; b=2; c=3;. ומכיוון שהחלק שדולג כהערה נמצא עדיין בתוך רכיב ה-script, הוא גם לא מוצג על המסך. זה בדיוק מה שעומד מאחורי הסימפטום של “16 ביקורות שנראות כמו 2”.
מה שצריך לקחת מכאן
אל תהפכו כאן את הסיבתיות. הגבלת מספר תווים בקלט נכשלת כטיפול ב-XSS לא מפני שמספר הפרסומים היה בלתי מוגבל, אלא מפני ש50 תווים מספיקים ממילא כדי להריץ script. הוספה של תכונת event handler אחת, או הצבה של תגית אחת שטוענת script חיצוני, נכנסת לעשרות תווים בכל אחד מהמקרים. גם אם מגבילים לפרסום אחד, הגבלת מספר תווים לא תהפוך להגנה.
אם כך, מה היו הפרסומים המפוצלים במקרה הזה? הם היו האמצעי להכניס script ארוך של כ-20 שורות בשלמותו. מה שהתוקף רצה לעשות היה ארוך, ולכן הוא פיצה על האורך בחזרות. זה כשלעצמו לא הסיבה שהגבלת מספר התווים נשברה. בין תיאור השיטה לבין הערכתה כטיפול צריך להפריד.
אותו דבר נכון להגבלות בצד הקלט בכלל.
- הגבלת מספר תווים, הגבלה על סוגי התווים שמותר להקליד, validation בצד הלקוח — אלה נדרשים לפי המפרט, אבל הם לא מחליפים את הפתרון היסודי ל-XSS
- לתוקף תמיד נשאר מרווח לעקוף הגבלות בצד הקלט: פיצול התוכן, קידוד, או הזרקה בנתיב אחר
- המקום שעלינו להגן עליו הוא הרגע שבו הנתונים יוצאים כ-HTML
הערות הציון: לא להניח שההגבלה הוסרה
הערות הציון של IPA אומרות על הסעיף הזה גם ש”נראו תשובות אחדות שמצביעות על בדיקה חסרה, כמו ‘התוקף מחק את הגבלת הקלט בכלי המפתחים ואז פרסם’”. נכון שאת ההגבלות בצד הלקוח אפשר להסיר בכלי המפתחים. אבל מה שהסעיף הזה ביקש הוא לקרוא מתוך העקבות שנשארו ב-HTML מה בעצם נעשה. ה-HTML באיור 3 הכיל 15 פרסומים נפרדים, כל אחד קצר מספיק כדי להיכנס בהגבלה, עם סימני הערה בהתחלה ובסוף של כל אחד. אלה עקבות של הגבלה שעוקפה, לא של הגבלה שהוסרה. צריך לקרוא את זה כשאלה על המשמעת של לבדוק, אחד-אחד, את מה שהתוקף השאיר אחריו.
החשיבות של קריאה כזו של הראיות מצוינת גם בהערות הציון.4
את החשיבה התכנונית של כמה ובאיזה אופן לאמת ערכי קלט, אפשר למצוא, כעיצוב שלא סומך על נתונים שמגיעים מבחוץ, גם באל תשתמשו בערך שמתקבל מקריאת QR code כמו שהוא ── הצלחה של תיקון שגיאות אינה מבטיחה את הערך.
6. מה ה-script עשה ── סעיף 3(1)
ה-script שמר N חילץ הוא באורך של כ-20 שורות. בסעיף 3(1) מבקשים לומר בעד 60 תווים מה עושות שורות 6 עד 20 מתוכו, כלומר החלק שרץ אחרי שהבקשה הראשונה הצליחה.
דוגמת התשובה: “ה-script מעלה את ה-session ID כתמונת פרופיל, יחד עם ה-token שהושג מה-response של ה-XHR”.3
סדר הקריאה הוא: להשיג את ה-token, להפוך את מחרוזת ה-cookie לקובץ, להעלות עם ה-token. הפרדה בין “מה” נשלח, “בתור מה” ו”לאן” מבליטה את נקודות המפתח של התשובה.
הבא הוא קוד שקול שאנחנו כתבנו כדי להסביר את ההתנהגות הזו (זה לא ציטוט מדויק של איור מחוברת השאלות). “שורות 6 עד 20” שבסעיף הן מספרי השורות בחוברת השאלות, ולא בדוגמה שלמטה. בדוגמה, הערה ① היא ההכנה, וההערות ② עד ④ תואמות לעיבוד שהסעיף שואל עליו.
// ① קודם משיגים את עמוד הגדרות הפרופיל
const xhr = new XMLHttpRequest();
xhr.open("get", "https://example.jp/user/profile");
xhr.responseType = "document"; // לקבל את ה-response כ-DOM ולא כטקסט
xhr.send();
xhr.onload = function () {
// ② לקרוא את ה-token ל-upload באותו הליך של המסך הלגיטימי
const token = xhr.response.getElementById("token").value;
// ③ להפוך את ה-cookie (שכולל את ה-session ID) ישירות לתוכן של קובץ PNG
const file = new File([document.cookie], "a.png", { type: "image/png" });
// ④ לשלוח לפונקציית העלאת תמונת הפרופיל של האתר עצמו
const form = new FormData();
form.append("uploadfile", file);
form.append("token", token);
const xhr2 = new XMLHttpRequest();
xhr2.open("post", "https://example.jp/user/upload");
xhr2.send(form);
};
חמש השורות הראשונות קיימות רק כדי לעבור את טיפול ה-CSRF של חברת Q
מה שראוי לתשומת לב ב-script הזה הוא שהמחצית הראשונה כולה מוקדשת להשגת ה-token.
חברת Q דרשה, בהעלאת תמונת פרופיל, ש”ה-token יהיה תואם לזה שהונפק ב-/user/profile”. זה מנגנון שנועד למנוע העלאה ביוזמת גורם זר מטופס מזויף שיושב באתר אחר, כלומר טיפול CSRF.
אבל ה-script התוקף רץ בתוך הדפדפן של הקורבן, על אותה origin כמו הקורבן. אם כך, די בכך שהוא יעשה בדיוק את מה שהמסך הלגיטימי עושה. לשלוח GET לעמוד הגדרות הפרופיל, ולקרוא את הערך של הרכיב בשם token שבתוכו. זה לבדו מניח את ה-token בידיו.
ה-token של CSRF לא מונע XSS. ברגע ש-XSS קיים, הקוד של התוקף יכול להתנהג כמשתמש לגיטימי מחובר. מה שה-token מגן עליו הוא בקשות שנשלחות בלי הרשאה מ-origin אחר, ולא script זדוני שרץ על אותה origin.
ההבחנה הזו משתלמת גם כשקוראים דרישות אבטחה בעבודה. “יש לנו CSRF token, אז אנחנו בסדר” עשוי להיות נכון לגבי CSRF, אבל הוא לא אומר דבר וחצי דבר על XSS.
מה המשמעות של האובייקט File
גם השורה השלישית (③) שווה מבט. היא בונה אובייקט קובץ שתוכנו הוא המחרוזת של document.cookie, ששמו a.png, וה-MIME type שלו image/png.
התוכן הוא סתם טקסט. זה לא קובץ PNG. ההעלאה בכל זאת הצליחה מפני שכפי שכתוב בשאלה, Web app Q לא בדקה את הפורמט של קובץ התמונה שהועלה.
למה אפשר היה לקרוא את ה-session ID, ומה בדיוק מגן עליו HttpOnly
והסיבה ש-document.cookie נקרא בכלל היא שה-cookie לא נשא את התכונה HttpOnly. RFC 6265 קובע שהתכונה HttpOnly “מגבילה את ההיקף של ה-cookie לבקשות HTTP” ובמיוחד “מורה ל-user agent להשמיט את ה-cookie כשניתנת גישה אל cookies דרך API שאינם HTTP (למשל API של דפדפן שחושף cookies ל-scripts)”.7 אילו התכונה הזו הייתה מוגדרת, ה-session ID לבדו היה נושר מהמחרוזת ששורה ③ תופסת, ומה שנשאר להוציא החוצה לא היה שווה דבר.
מה שצריך לשים לב אליו כאן הוא ש-HttpOnly מסתיר רק את ה-cookies שהתכונה הזו מוגדרת עליהם. אם על אותה origin יש cookies בלי HttpOnly, כמו העדפות תצוגה או מזהי מדידה, document.cookie ימשיך להחזיר גם אותם. הגדרת HttpOnly לא הופכת את document.cookie לריק. מה שרוצים להגן עליו הוא ה-session ID, ולכן צריך לבדוק בנפרד שה-cookie של ה-session ID אכן נושא אותה.
7. הוצאת מידע שלא יוצאת מהאתר ── סעיפים 3(2) ו-3(3)
בסעיף 3(2) שואלים בעד 50 תווים איך התוקף יכול להשיג את המידע שהועלה.
דוגמת התשובה: “להוריד את תמונת הפרופיל של החבר ולהוציא ממנה את מחרוזת ה-session ID”.3
הבסיס לאיסוף נמצא במקום שבו ה-upload מוצג
תזכרו את מה שכתוב במפרט בשאלה.
תמונת הפרופיל שהועלתה מוצגת בעמוד הגדרות הפרופיל של החבר ובעמודי הביקורות.
כלומר, “תמונת הפרופיל” שכתוב בתוכה ה-session ID של הקורבן מונחת במקום שאפשר לצפות בו באתר. התוקף לא צריך לעשות שום דבר מיוחד. לפתוח עמוד שבו מוצגת תמונת הפרופיל של הקורבן, ולקחת את ה-URL של אותה תמונה. התוכן הוא טקסט, ולכן פתיחה שלו חושפת את ה-session ID.
נוסיף לכך שהאיסוף הזה הוא כל כך פשוט כשתמונת הפרופיל של הקורבן מופיעה בעמוד שהתוקף רואה, למשל בעמוד הביקורות של מוצר שגם התוקף כתב עליו ביקורת. ה-URL של תמונת פרופיל קבוע לכל חבר, ולכן ברגע שרואים אותו במקום כלשהו, אפשר לגשת ישר לאותו URL מכאן והלאה. כך או כך, כל מה שהתוקף עושה הוא GET לעמוד או לתמונה כמבקר לגיטימי, ומנקודת המבט של האתר זו אינה תעבורה חריגה.
לעקוב אחרי הזרימה כולה, מהפרסום ועד האיסוף
מי שמעלה הוא הדפדפן של הקורבן, ומי שאוסף הוא התוקף. עקבו אחרי התרשים כשהפעולות של שני הצדדים נפרדות.
sequenceDiagram
autonumber
participant AT as התוקף
participant Q as Web app Q<br/>(example.jp)
participant V as הדפדפן של הקורבן
Note over AT,Q: שלב ההכנה
AT->>Q: לפרסם את הביקורת ב-15 חלקים<br/>(סימני הערה מדלגים על ה-HTML)
Note over Q: לשמור את המחרוזת שפורסמה<br/>כמו שהיא<br/>(השמירה עצמה כאן תקינה)
Note over V,Q: הקורבן פותח את עמוד V
V->>Q: GET עמוד V
Note over Q: ★החולשה כאן★<br/>הטמעת הכותרת השמורה<br/>לתוך ה-HTML בלי escaping
Q-->>V: HTML שמכיל את ה-script התוקף
Note over V: רץ כרכיב script
V->>Q: GET /user/profile
Q-->>V: HTML שמכיל את ה-token
Note over V: לקרוא את document.cookie<br/>ולהפוך אותו לתוכן של a.png
V->>Q: POST /user/upload<br/>(uploadfile=a.png, token=…)
Note over Q: לשמור בלי לבדוק את הפורמט<br/>ולפרסם כתמונת פרופיל
Note over AT,Q: איסוף
AT->>Q: GET תמונת הפרופיל של הקורבן
Q-->>AT: קובץ שכתוב בתוכו ה-session ID
טיפולי יציאה (egress) לא עוצרים את זה
כשמציירים את זה, מה שהופך את הנתיב הזה למסובך נעשה ברור. הדפדפן של הקורבן מתקשר מההתחלה ועד הסוף רק עם האתר הלגיטימי (example.jp).
הטבלה הבאה משווה בין טיפולים שמשגיחים על יעדים חיצוניים חשודים לבין טיפולים שעוצרים הרצה של script. מבין ההגדרות שמופיעות כאן, רק האחרונה עוזרת. CSP אינו תנאי מהשאלה, וקראו אותו כהשלמה לעבודה המעשית.
| טיפול | מול התקיפה הזו | למה |
|---|---|---|
| סינון URL ב-proxy | לא עוזר | היעד היחיד שאליו ניגשים הוא אתר e-commerce לגיטימי מבחינה עסקית |
| ניטור תעבורה יוצאת ב-Firewall | לא עוזר | לא נוצרת תעבורה לדומיין לא מוכר |
הגבלת connect-src של Content Security Policy (הגדרה כמו 'self' שמתירה את אותה origin) |
לא עוזר | שתי הבקשות מכוונות לאותה origin ונכנסות למה שהמדיניות מתירה |
הגבלת script-src של Content Security Policy (הגדרה שלא מתירה 'unsafe-inline') |
עוזר | ה-script inline שהוטבע לא רץ מלכתחילה |
כשמדברים על הוצאת מידע דרך XSS, נוטים לדמיין תמונה של cookies נשלחים לשרת של התוקף, אבל השאלה הזו מראה בבירור שיעד ההוצאה יכול להיות האתר של הקורבן עצמו. בכל מקום באתר שאפשר גם לכתוב אליו וגם לקרוא ממנו, אותו מקום הופך לנקודת מסירה. פונקציית העלאת קבצים, שדה פרופיל פומבי ושדה הערות פומבי הם הדוגמאות האופייניות.
ב-CSP מפרידים בין “לאן מותר לדבר” ל”מה מותר להריץ”
השלמה לגבי CSP: מבין ההגדרות שבשימוש נפוץ בעבודה, זו שתעצור את התקיפה הזו באופן ודאי היא script-src ולא connect-src, שמגביל את יעדי התקשורת. בהגדרה שמציינת script-src 'self' ולא מתירה 'unsafe-inline', ה-<script> ה-inline שהוטבע בביקורת לא רץ מלכתחילה. מי שמבין את CSP רק כ”משהו שמצמצם תעבורה החוצה” יפספס את ההבדל הזה.8
גם connect-src אינו חסר אונים באופן עקרוני. הגדרה כמו connect-src 'none', שאוסרת אפילו תקשורת לאותה origin, או רשימת הרשאות מצומצמת לנקודות קצה מסוימות, תעצור גם את שני ה-XHR האלה. אבל מעט אתרים יכולים לצמצם עד כדי כך, ולכן כהכנה מציאותית script-src הוא המועמד המרכזי. ההבנה המדויקת היא לא “אותה origin מחוץ לתחום של CSP” אלא “הגדרה נפוצה שמתירה את אותה origin נותנת לזה לעבור”.
סעיף 3(3): מה אפשר לעשות עם ה-session ID
בסעיף 3(3) שואלים בעד 40 תווים מה אפשר לעשות עם המידע שהושג.
דוגמת התשובה: “להתחזות לחבר שניגש לעמוד V ולהשתמש בפונקציות של Web app Q”.3
לפי הערות הציון, שיעור התשובות הנכונות לסעיף הזה היה גבוה. נכתב שם שההשפעה של כך שתוקף משיג cookies באתר e-commerce הייתה מובנת היטב.4
ואם חוזרים למפרט שבשאלה, לפונקציית פרופיל החבר יש במפורש עמוד לרישום פרטי כרטיס אשראי, שזמין רק לחברים מחוברים. מה שמסתתר מעבר להתחזות היה מוכן בשאלה מההתחלה.
8. למה זה לא עובד מהאתר של התוקף ── סעיף 4
סעיף 4 הוא השאלה היסודית ביותר בכל השאלה הזו.
נניח שהתוקף הכין באתר בדומיין שלו HTML שמכיל את אותו script כמו באיור 4, וחבר מחובר של Web app Q ניגש לאותו אתר. גם אז התקיפה לא תצליח, בגלל מנגנון בדפדפן ה-web. תארו את המנגנון הזה בעד 40 תווים.
דוגמת התשובה: “המנגנון שבו cookie לא נשלח מ-script ל-URL בדומיין אחר”.3
בפרק הזה נבדוק קודם מה יקרה אם ה-script נשאר בדיוק כמו בשאלה, ואחר כך נסדר את התנאים סביב CORS ו-SameSite. חשוב לא לקבוע, כבסיס לתשובה, הגדרות שלא כתובות בשאלה.
הבסיס לתשובה: ל-XHR הראשון אין cookie
נניח שאותו script מונח על evil.example וחבר של חברת Q מנווט אליו. מה יקרה?
קודם כל, הבקשה הראשונה נופלת. כש-script שיושב על evil.example שולח XMLHttpRequest אל https://example.jp/user/profile, זו בקשת cross-origin.
החלק המכריע הוא שלא מצורף cookie. ה-script באיור 4 לא מגדיר withCredentials, ולכן cookie לא מצורף לבקשת cross-origin. מנקודת המבט של השרת של חברת Q זו גישה בלי התחברות, ועמוד הגדרות הפרופיל שמכיל את ה-token לא חוזר. ה-script לא יכול להשיג את ה-token, והוא נעצר שם.9
השלמה מותנית: CORS קובע אם אפשר לקרוא את ה-response
יש גם קיר שני: אי אפשר לקרוא את ה-response. אם חברת Q לא מתירה את evil.example ב-Access-Control-Allow-Origin, הדפדפן נכשל בבדיקת CORS, מתייחס לבקשה הזו כשגיאת רשת, ו-onload לא נורה — ולכן ה-script לא מגיע ל-xhr.response. אתר רגיל לא מחזיר את הכותרת הזו ל-origin לא מוכר, ולכן בפועל גם כאן זה אמור להיעצר.
איך חברת Q הגדירה את CORS, לעומת זאת, לא כתוב בשאלה. גם אילו היא התירה גישה מ-evil.example, מה שאפשר היה לקרוא הוא ה-response במצב לא מחובר, ובתוכו אין token, ולכן התקיפה גם אז לא הייתה מצליחה. הנקודה הראשונה לבדה — שאין cookie מצורף — כבר עוצרת את התקיפה.
העובדה ש-cookie לא מצורף היא סיבה שאפשר לקרוא מתוך ה-script הזה. האם גם הגבלת CORS חלה — זה תלוי בהגדרות של חברת Q. את שני הדברים האלה אין להתייחס כאל עובדות מאומתות.
לקרוא cookie ולצרף cookie לבקשה הם שני דברים שונים
בנוסף, גם את ה-cookie עצמו אי אפשר לקרוא. מה ש-document.cookie מחזיר הוא ה-cookies של ה-origin שעליו רץ ה-script, כלומר evil.example. ה-session ID שהונפק עבור example.jp לא נמצא ביניהם.
דוגמת התשובה של IPA לוקחת את הראשון, כלומר את זה ש-cookie לא נשלח ל-URL בדומיין אחר, כתשובה.
וכאן אל תזכרו את שני הדברים האלה כמנגנון אחד. אלה מנגנונים נפרדים.
document.cookieלא מחזיר את ה-cookies שלexample.jpמפני שcookies מנוהלים בהפרדה לפי דומיין. בין אתרים שונים, כלומר בין דומיינים רשומים שונים כמוevil.exampleו-example.jp, אי אפשר להרפות את ההפרדה הזו בשום הגדרה. תת-דומיינים תחת אותו דומיין הם עניין אחר: עם התכונהDomainשל ה-cookie אפשר לשתף cookie ביןsub.example.jpל-example.jp7- cookie לא מצורף ל-XHR של cross-origin מפני ש-
withCredentialsמוגדר כברירת מחדל ל-false. זה כן משתנה כשהתנאים מסתדרים. אם ה-script מגדירwithCredentials = true, וגם התכונהSameSiteשל ה-cookie מתירה שליחה cross-site, וגם השרת מחזירAccess-Control-Allow-Originשמציין במפורש את ה-origin ששלח יחד עםAccess-Control-Allow-Credentials: true— אז ה-cookie נשלח וגם אפשר לקרוא את ה-response
השלמה מעשית: מה נדרש כשמשנים את ההגדרות
כאן צריך להפריד בין הסיבה שבגללה זה נכשל בפועל לבין מה שנדרש אילו הכתיבו את ה-script מחדש.
יש דבר אחד שאפשר לומר בבירור מתוך השאלה ומתוך איור 4. withCredentials לא מוגדר, ולכן cookie לא מצורף, הגישה נחשבת ללא מחוברת, וה-token לא מושג. זה לבדו עוצר את התקיפה.
היעדר הרשאה ב-CORS הוא קיר נוסף שמונע קריאה של ה-response, אבל ההגדרות של חברת Q לא כתובות בשאלה, ולכן את זה יש לראות כחיזוק מותנה מהסוג של “ככה אתר רגיל אמור להיות מוגדר”.
אז מה קורה אם התוקף משכתב את זה ל-withCredentials = true? רק אז נכנס לתמונה התנאי הנוסף. אם ה-SameSite של ה-cookie לא מתיר שליחה cross-site, גם הרמה של withCredentials תשאיר את ה-cookie הזה לא מצורף. הדפדפנים של היום מתייחסים ל-cookie בלי SameSite כ-Lax, ולכן כברירת מחדל הוא נופל לצד של “לא מצורף”.
גם את זה, איך הוגדר SameSite על ה-cookie של ה-session ID בחברת Q, לא כתוב בשאלה. ולכן אי אפשר לומר שהאתר “היה מוגן בזכות SameSite”. הנקודה היא רק ש-SameSite יכול לעבוד מול תוקף שמגיע עם credentials.
לסיכום, כדי לקרוא response מאומת מדומיין אחר צריכים להתקיים שלושת אלה יחד: withCredentials = true, ה-SameSite של ה-cookie שמתיר שליחה cross-site, ושרת שמתיר את ה-origin ששלח ב-CORS עם credentials. דומיין שונה אינו אוטומטית בטוח, אבל גם לא משהו שדי באחד מהתנאים האלה כדי לשבור. כשעוברים על האתר שלכם, אל תתנחמו בהפרדה של cookies לפי דומיין; צריך לבדוק בפועל את הגדרות ה-CORS — במיוחד לאילו origins מותרת בקשה עם credentials — ואת התכונה SameSite של ה-cookie.
לכן ה-Stored XSS כל כך שווה
מה שהסעיף הזה מלמד הוא למה התוקף מתעקש להריץ את הקוד שלו בתוך האתר של הקורבן.
גם אם תכינו בדומיין שלכם אתר מזויף שנראה זהה לחלוטין, הדפדפן יתייחס אליו כאתר אחר. אי אפשר לקרוא את ה-cookies של האתר השני דרך document.cookie, ועם XHR שנשלח בלי credentials — כמו ה-script של הסעיף הזה — האתר השני אפילו לא רואה את הבקשה ככזו שמחוברת. הערך מבחינת התוקף נמצא בכך שהקוד רץ על ה-origin של אתר הקורבן עצמו. Stored XSS הוא בדיוק אמצעי שמשיג את זה.
לא לבלבל בין “אפשר לשלוח” ל”אפשר לקרוא”
אבל אל תקראו את זה כ”מדומיין אחר אי אפשר לשלוח שום בקשה מאומתת”. אם ה-SameSite של ה-cookie מוגדר כך שהוא מתיר שליחה cross-site, למשל SameSite=None, עמוד זדוני כן יכול להעביר בקשה עם cookies לשרת של השני. POST מטופס הוא המקרה האופייני: מה ש-CORS שולט בו הוא בעיקר האם script יכול לקרוא את ה-response, ולא האם הבקשה מגיעה לשרת (אם כי כשמוסיפים header מותאם אישית או שולחים את הגוף כ-application/json, נשלחת קודם בקשת preflight, ואם היא לא מאושרת הבקשה עצמה בכלל לא נשלחת). זו הסיבה לכך שתקיפת CSRF עובדת, ולכן גם נדרש token של CSRF.
במילים אחרות, מה ש-script מ-origin אחר לא יכול לעשות כברירת מחדל הוא לקרוא את ה-cookies של הצד השני ולקרוא את ה-response — ולא לשלוח בקשה.
ויותר מזה, לשני ה”לא יכול לקרוא” האלה יש חוזק שונה. ההפרדה לפי דומיין שמאחורי document.cookie אי אפשר להרפות בהגדרה בצד השרת, ואילו האם אפשר לקרוא את ה-response תלוי בהגדרת ה-CORS של השרת השני. אם השרת מתיר את ה-origin ששלח, כלומר מחזיר origin מפורש ב-Access-Control-Allow-Origin ובקשה עם credentials גם מחזיר לצידו Access-Control-Allow-Credentials: true, אז גם script מ-origin אחר יכול לקרוא את ה-response. זו בדיוק הסיבה שצריך לבדוק את הגדרות ה-CORS של האתר שלכם.
מה שמיוחד ב-XSS הוא שהוא מדלג מעל כל התנאים האלה ופועל מלכתחילה כ-origin של אתר הקורבן.
במילים אחרות, XSS הוא לא “תקלה שבה מופיעים תווים מוזרים על המסך”, אלא תקלה ששמה את התוקף באותו מעמד של משתמש לגיטימי באתר שלנו. הפער בהבנה הזו הוא מה שמפריד בין דירוגי עדיפות שונים.
9. טיפולים שעבדו וטיפולים שלא עבדו
הנה כל מה שהיה עד כאן בטבלה אחת. זה החלק במאמר שאנחנו הכי רוצים שתיקחו איתכם. קראו אותו לפי “איזה שלב של התקיפה הזו הטיפול עוצר”, ולא לפי “האם יש טיפול”.
קודם משווים את הטיפולים של חברת Q מול התקיפה הזו
| מנגנון שהיה לחברת Q או שלא היה | מול התקיפה הזו | למה |
|---|---|---|
| תקשורת ב-HTTPS | לא עזר | הוא מגן על ערוץ התקשורת, ואין לו שום קשר ל-script שמוטמע בעמוד |
| הגבלת קלט של 50 תווים לכותרת הביקורת ו-300 לתוכן הביקורת | לא עזרה | ה-payload פורסם ב-15 פרסומים, וה-HTML שביניהם דולג כהערת JavaScript |
| ה-token ל-upload (טיפול CSRF) | לא עזר | script על אותה origin יכול להשיג את ה-token בהליך הלגיטימי |
| פונקציית upload שדורשת התחברות | לא עזרה | מי שפועל הוא הדפדפן של הקורבן עצמו, והוא מחובר |
| escaping בזמן הפלט (לא היה) | עוזר (הפתרון היסודי) | המחרוזת שפורסמה כבר לא מפורשת כ-HTML, ולכן ה-script בכלל לא רץ |
| התכונה HttpOnly על ה-cookie (לא הייתה) | עוזר (אמצעי הגנה משני) | את ה-session ID כבר אי אפשר לקרוא מ-document.cookie |
| בדיקת פורמט ו-re-encode של קבצים שמועלים (לא היו) | עוזר (אמצעי הגנה משני) | טקסט גולמי לא יכול להישמר כ-PNG, ומחרוזת שמוסתרת במטא-דאטה נמחקת ב-re-encode. אבל מידע שמקודד כפיקסלים של התמונה נשאר |
ארבעת השורות הראשונות לא עשו דבר מול התקיפה הזו. אם מתרגמים את שלוש השורות האחרונות לקטגוריות של “איך לבנות אתר בטוח” של IPA, הראשונה היא פתרון יסודי והשתיים האחרות הן אמצעי הגנה משניים.5
הערה אחת: זה לא אומר שארבעת הראשונים הם דברים ש-IPA לא מכירה כטיפולים. הקטגוריות של IPA הן פתרון יסודי ואמצעי הגנה משניים, ובדיקת התוכן של ערכי הקלט מופיעה דווקא כאמצעי הגנה משני. אבל יש שם הסתייגות ש”הטיפול הזה יעיל רק במקרים מוגבלים”, והמקרה הזה נפל בדיוק בתוך המגבלה הזו.
כל אחד עוצר שלב אחר, ולכן מדובר בהגנה רב-שכבתית
והנקודה החשובה היא שכל אחד משלושת האחרונים לבדו היה שובר את שרשרת התקיפה שבוצעה בשאלה הזו.
- אם היה escaping, ה-script לא רץ בכלל
- אם HttpOnly היה מוגדר, ה-script רץ אבל לא יכול לקרוא את ה-cookie
- אם הייתה בדיקת פורמט, את ה-cookie אפשר לקרוא אבל אי אפשר להעלות טקסט גולמי כ-PNG
בדיוק במצב כזה יש משמעות להגנה רב-שכבתית. אמצעי הגנה משניים עדיין לא מחליפים את הפתרון היסודי. גם עם HttpOnly, אם ה-XSS נשאר, התוקף יכול לבצע בדפדפן של הקורבן פעולות שרירותיות, למשל לקנות מוצרים או לשנות מידע רשום. תקיפות שלא צריך בהן בכלל לגנוב את ה-session ID אפשר לתכנן בלי סוף.
למה הטבלה אומרת גם re-encode ולא רק בדיקת פורמט
יש סיבה שהשורה האחרונה בטבלה כתובה בשני שלבים: “בדיקת פורמט ו-re-encode”.
להבדיל בין טקסט גולמי במקרה הזה לבין תמונה בפורמט תקין
חברת Q בשאלה לא בדקה בכלל את הפורמט של קבצי תמונה, ולכן עצם הוספה של בדיקת פורמט עוצרת את התקיפה הזו. אבל זה נכון רק למקרה שבו התוקף שולח טקסט גולמי. תוקף יכול גם להרכיב קובץ PNG תקין ולהחביא את המחרוזת באזור המטא-דאטה שלו או במקום דומה. במקרה כזה הבדיקה “האם הפורמט תקין” עוברת.
ולכן, כדי לצמצם את הנתיב הזה, צריך להגיע עד כדי re-encode לתמונות שמועלות בצד השרת והגשתן מחדש, כלומר לא לשמור את סדרת הבייטים המקורית כמו שהיא.
גם re-encode אינו פתרון מושלם
גם re-encode לא סוגר את הנתיב לחלוטין. אם התוקף מצייר את ה-session ID בתור הפיקסלים של התמונה עצמה ובונה PNG תקין, re-encode רגיל משמר את המידע הנראה הזה, ולכן התוקף יכול להוריד את התמונה ולקרוא אותו. מה ש-re-encode עוצר הוא הווריאנט ששולח סדרת בייטים גולמית והווריאנט שמסתיר במטא-דאטה — ולא מידע שמקודד כתוכן התמונה.
במילים אחרות, כל עוד באתר יש מקום שאפשר גם לכתוב אליו וגם לקרוא ממנו, אי אפשר לסגור אותו לגמרי כנתיב הוצאה. זה מתאר היטב את האופי של אמצעי הגנה משניים. אמצעי כזה צריך לעצור לא רק את התקיפה שנצפתה עכשיו, אלא להיות מתוכנן תוך מבט על האם הוא עוד מחזיק כשהתוקף משנה את השיטה במקצת. וכמה שלא תערמו אותם, הם לא מחליפים את ה-escaping בזמן הפלט, שהוא הפתרון היסודי.
הגשה מדומיין נפרד היא טיפול באיום אחר
עיצוב שבו הגשת הקבצים שהועלו נעשית מדומיין נפרד מהאתר הראשי הוא גם המלצה נפוצה, אבל הוא אינו טיפול בנתיב ההוצאה הזה. התוקף יכול להוריד את התמונה גם מדומיין נפרד באותה קלות, ו-same-origin policy אינו מנגנון ששומר בסוד סדרות בייטים שמפורסמות. מה שהגשה מדומיין נפרד כן עוצרת הוא האיום שהקובץ שהועלה עצמו רץ כתוכן פעיל על ה-origin של אתר הקורבן, מהסוג שבו מעלים HTML או SVG. צריך להבין את זה בנפרד, כטיפול באיום אחר.
“להכריע לפי התוכן” נראה שונה בכל פורמט קובץ
הרעיון לשפוט קובץ לפי התוכן שלו ולא לפי הצהרת השולח (סיומת או Content-Type) הוא עיקרון בסיסי שנדרש הרבה מעבר ל-web. אבל מה שנחשב ל”לשפוט לפי התוכן” תלוי בפורמט. בפורמט כמו PNG, שיש לו סדרת בייטים קבועה בהתחלה, אפשר לבדוק אותה. לעומתם, ל-CSV אין חתימה תקנית, ו-BOM בהתחלה מעיד רק על קידוד התווים ואינו הוכחה שהתוכן הוא CSV. כפי שמוסבר בטיפול בקבצים מסוג CSV, ב-CSV הדבר מסתכם בבדיקה האם הוא נפרס לפי הדיאלקט המצופה והאם הוא נשאר בתוך הסכימה המצופה ובגבלת מספר הרשומות.
10. נקודות בדיקה בקוד שלכם
אם מתרגמים את השאלה הזו לרשימת בדיקה לסקירת קוד, מקבלים את הדבר הבא. בכל אפליקציית web שיש בה אזור חברים ופונקציית פרסום אפשר להשתמש בזה כמו שזה.
סעיפים 1 עד 3 עוברים על הקלט והפלט, סעיף 4 על ה-cookie, סעיפים 5 ו-6 על ה-upload, וסעיפים 7 ו-8 על CSP ועל ה-token. המחצית הראשונה בודקת את המימוש שמסיר את הסיבה השורשית; המחצית השנייה בודקת את הטווח שבו אמצעי הגנה משניים עובדים.
- האם escaping בזמן הפלט עובד בכל נקודת פלט? לרשום כל מקום שבו ה-escaping האוטומטי של מנוע התבניות מבוטל במפורש (
raw,| safe,dangerouslySetInnerHTMLוכדומה), ולבדוק שאפשר להסביר לכל אחד מהם למה ביטול הזה מקובל - האם אתם סופרים הגבלות בצד הקלט כטיפול ב-XSS? הגבלת מספר תווים והגבלה על סוגי התווים שמותר להקליד הן דרישות של המפרט, ולא הפתרון היסודי ל-XSS
- האם אתם עושים escaping בזמן הקלט? התפקיד של צד הקלט הוא validation שדוחה ערכים מחוץ למפרט, ולא escaping. בזמן הקלט עוד לא נקבע היעד (HTML, CSV, JSON, מייל, log), ולכן escaping בזמן הקלט מזמין הכפלת escaping וקלקול נתונים. גם בבניית משפטי SQL זה אותו דבר: הפתרון היסודי שם הוא placeholders
- האם ל-cookies יש
HttpOnly,Secureו-SameSite? במיוחד כדאי לבדוק את ה-cookie של ה-session ID - האם אתם בודקים את הפורמט של קובץ שהועלה לפי התוכן ולא לפי הסיומת או ה-Content-Type? מידע שהשולח מצהיר עליו אינו חומר לבדיקה. אבל בדיקת פורמט לבדה נותנת לעבור לשיטה של החדרת מידע לאזור המטא-דאטה של תמונה תקינה
- האם אתם עושים re-encode לתמונות שמועלות לפני ההגשה? האם אתם מחזירים את סדרת הבייטים המקורית כמו שהיא? חשוב להבין שגם re-encode משאיר מידע שמקודד כפיקסלים, ולכן מקום שאפשר גם לכתוב אליו וגם לקרוא ממנו אי אפשר לסגור לגמרי. חשוב גם שהגשה מדומיין נפרד היא טיפול באיום של קובץ שהועלה שרץ על ה-origin של האתר שלכם, ולא בנתיב ההוצאה הזה
- האם ההגדרה שלכם יודעת לעצור scripts inline דרך CSP? הקריטריון אינו “האם כתוב שם
'unsafe-inline'” אלא “האם scripts inline באמת מותרים”. כשמופיעים ב-script-srcnonce או hash, דפדפנים מ-CSP Level 2 ומעלה מתעלמים מ-'unsafe-inline', ולכן במדיניות ששומרת על תאימות לאחור ומפרטת גם'unsafe-inline'וגם nonce, script שהוזרק בלי ה-nonce נחסם8 - האם אתם יכולים להסביר את מה שה-token מגן עליו? token של CSRF לא מונע XSS. כל אחד מהם צריך טיפול נפרד משלו
הסעיף הראשון במיוחד הוא תבנית אופיינית שעולה בחקירות אמיתיות. לא נדיר למצוא צוות שהאמין שהוא בטוח מפני שה-framework עושה escaping אוטומטי, ובפועל ה-escaping האוטומטי הושבת בדיוק במקום אחד בגלל דרישה נקודתית ל”להכניס HTML כמו שהוא”.
סיכום ── היכולת האמיתית שהשאלה הזו בוחנת
רשימת הטיפולים שנסקרה כאן מסוכמת ב”קודם כל, המסקנות” בראש המאמר. לסיום, מילה אחת על השאלה עצמה.
מה שעשוי היטב בשאלה כזו בבחינה הוא שהיא לא שואלת “האם אתם יודעים מה זה XSS” אלא “האם אתם יכולים להבדיל איזה מהטיפולים שכבר יש לכם באמת עובד”.
זה לא שחברת Q לא עשתה כלום. היא תקשרה ב-HTTPS, הציבה הגבלות מספר תווים על הקלט, דרשה token ל-upload, והגבילה את הפונקציות לחברים מחוברים. ברשימה זה נראה כמו מערכת מוגנת למדי. ובכל זאת הכול נעקף. להיפך, שלושת הדברים שחסרו לה הם כולם לא מרשימים, ואף אחד מהם אינו סוג של פונקציה שנכנסת לרשימת עדכון גרסה.
הדיון על אבטחה קשה מפני שמספר הטיפולים אינו פרופורציונלי לחוזק ההגנה. השאלה היא האם אתם יכולים לומר, אחד-אחד, לא “מה אנחנו עושים” אלא “איזה שלב של איזו תקיפה הטיפול הזה עוצר”. זו לא יכולת שנדרשת רק לבחינת הסמכה; זה שיקול הדעת הנדרש ביותר גם למי שמקבל הסבר על אמצעי אבטחה וגם למי שמיישם אותם.
תחומי ייעוץ קשורים
חברת KomuraSoft LLC עוסקת בבניית אתרים עם אזור חברים ופונקציית פרסום, ובסקירת תכנון שבודקת אם באפליקציית web קיימת יש את אותה חולשה.
מקורות
-
IPA (Information-technology Promotion Agency, Japan), חוברות שאלות, חלוקת נקודות, דוגמאות תשובה והערות ציון (שנת 2023, Reiwa 5), ובתוכה “מבחן Registered Information Security Specialist, סתיו 2023 (Reiwa 5), אחר-הצהריים, שאלות”. על הפונקציות של Web app Q של חברת Q (רישום חברים, התחברות והנפקת session ID כ-cookie, הגבלת קלט של 50 תווים לכותרת הביקורת ו-300 לתוכן הביקורת בפונקציית ביקורות המוצר, העלאת תמונת פרופיל ורישום פרטי כרטיס אשראי בפונקציית פרופיל החבר, העובדה שהעלאת תמונת הפרופיל מקבלת קובץ תמונה ו-token כפרמטרים, והעובדה שתמונת הפרופיל שמועלית מוצגת בעמוד הגדרות הפרופיל של החבר ובעמודי הביקורות), על התופעה שבה בעמוד V הוצגו רק 2 מתוך 16 הביקורות, על כך שה-HTML של עמוד V הכיל 15 פרסומים של חבר A ו-script ארוך, על תוכן ה-script שחולץ, ועל כך של-Web app Q הייתה חולשה שבה script שחבר הכניס הורץ, שתכונת HttpOnly לא הוגדרה על ה-cookie, ושהפורמט של קובץ התמונה שהועלה לא נבדק. גם הנוסח של סעיפים 1 עד 4 בא מחוברת זו. ↩ ↩2
-
IPA (Information-technology Promotion Agency, Japan), שאלות נפוצות על הבחינות. על כך שלגבי השימוש בשאלות בחינה מן העבר ש-IPA מפרסמת אין צורך באישור או בתשלום, למעט מקרים שבהם החוק קובע אחרת; על כך ש-IPA בכל זאת לא ויתרה על זכויות היוצרים; על כך שיש לציין את המקור בצורה “שנת מס, מחזור, סוג הבחינה, מקטע הזמן, מספר השאלה וכדומה” (הדוגמה שניתנת היא “מקור: מבחן Fundamental Information Technology Engineer, אביב 2019 (Heisei 31), בוקר, שאלה 1”); ועל כך שחייבים לציין גם כששונו חלקים מהשאלה. ↩
-
IPA (Information-technology Promotion Agency, Japan), דוגמאות תשובה למבחן Registered Information Security Specialist, סתיו 2023 (Reiwa 5). על כוונת השאלה (היא לוקחת כנושא תגובה לתקרית שבה נוצלה חולשה בתוכנית יישום web, ובוחנת את היכולת לקרוא מתוך HTML ו-ECMAScript את החולשה שנוצלה ואת הבעיות שמאחוריה ולתכנן טיפולים), ועל דוגמת התשובה של כל סעיף (סעיף 1(1): “(ב) Stored XSS”; סעיף 1(2): “לבצע escaping על כותרת הביקורת לפני הפלט.”; סעיף 2: “התוקף פרסם את התוכן בכמה פרסומים מפוצלים, כך שה-HTML הפך להערה והכול התחבר ל-script אחד.”; סעיף 3(1): “ה-script מעלה את ה-session ID כתמונת פרופיל, יחד עם ה-token שהושג מה-response של ה-XHR.”; סעיף 3(2): “להוריד את תמונת הפרופיל של החבר ולהוציא ממנה את מחרוזת ה-session ID.”; סעיף 3(3): “להתחזות לחבר שניגש לעמוד V ולהשתמש בפונקציות של Web app Q.”; סעיף 4: “המנגנון שבו cookie לא נשלח מ-script ל-URL בדומיין אחר”). ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
IPA (Information-technology Promotion Agency, Japan), הערות ציון למבחן Registered Information Security Specialist, סתיו 2023 (Reiwa 5). על כך ששיעור התשובות הנכונות בשאלה 1 בכללותה היה ממוצע; על סעיף 1(1), ש”נראו נבחנים אחדים שענו בטעות ‘DOM Based XSS’, כנראה מפני שה-script השתמש ב-DOM”, יחד עם ההערה שחולשות יש להבין בדיוק, כולל המאפיינים והטיפולים שלהן; על סעיף 2, ש”נראו תשובות אחדות שמצביעות על בדיקה חסרה, כמו ‘התוקף מחק את הגבלת הקלט בכלי המפתחים ואז פרסם’”, יחד עם ההערה שיש לטפח את היכולת לבדוק בקפידה את העקבות שהתוקף משאיר ולגבש תמונה מדויקת של שיטת התקיפה; ועל כך ששיעור התשובות הנכונות בסעיף 3(3) היה גבוה, וההשפעה של כך שתוקף משיג cookies באתר e-commerce הייתה מובנת היטב. ↩ ↩2 ↩3 ↩4 ↩5
-
IPA (Information-technology Promotion Agency, Japan), איך לבנות אתר בטוח. על כך שלגבי 11 סוגי חולשות באתרים היא מציגה את האיומים ואת הטיפולים כשהם מחולקים ל”פתרון יסודי” (מימוש שמסיר את הסיבה של החולשה עצמה) ול”אמצעי הגנה משני” (אמצעי שמנמיך את שיעור ההצלחה של תקיפה, או את הנזק, כשהחולשה נשארת); על כך שביצוע escaping לכל רכיב שמוצג לעמוד ה-web מופיע כפתרון היסודי ל-cross-site scripting; ועל רשימת הבדיקה ליישום אבטחה שמצורפת אליו ועל החוברות הנפרדות “איך לקרוא ל-SQL בבטחה” ו-“מפרט בדיקת בריאות האתר”. ↩ ↩2 ↩3
-
IPA (Information-technology Promotion Agency, Japan), איך לבנות אתר בטוח - 1.1 הזרקת SQL. על כך ש”לממש את כל בניית משפטי ה-SQL עם placeholders” מופיע כפתרון היסודי להזרקת SQL, כאשר placeholders סטטיים (prepared statements) נחשבים לצורה שבה עקרונית לא יכולה להיווצר חולשה; על כך שכמימוש למקרה שבו משפט SQL נבנה בשרשור מחרוזות כתוב “כשמרכיבים משפט SQL בשרשור מחרוזות, יש להשתמש ב-API של מנוע מסד הנתונים שמבצע escaping וכדומה, כדי לבנות נכון את ה-literals של משפט ה-SQL”, כך שה-escaping מיושם על יצירת ה-literals שמרכיבים את משפט ה-SQL ולא ברגע שבו מקבלים את הקלט; ועל כך שלצד אלה מופיע “לא לציין משפטי SQL ישירות בפרמטרים שנמסרים לאפליקציית ה-web” כפתרון יסודי, ואילו “לא להציג הודעות שגיאה בדפדפן כמו שהן” ו”לתת הרשאות מתאימות לחשבון מסד הנתונים” מופיעים כאמצעי הגנה משניים. ↩
-
IETF, RFC 6265: HTTP State Management Mechanism, Section 4.1.2.6 “The HttpOnly Attribute”. על כך שתכונת HttpOnly מגבילה את ההיקף של ה-cookie לבקשות HTTP, ובמיוחד מורה ל-user agent להשמיט את ה-cookie כשניתנת גישה אל cookies דרך API שאינם HTTP, כגון API של דפדפן שחושף cookies ל-scripts. ועל כך שתכונת Secure (Section 4.1.2.5) מגבילה את שליחת ה-cookie לערוץ מאובטח בלבד. ↩ ↩2
-
W3C, Content Security Policy Level 3. על השליטה בהרצה של script inline דרך script-src, על השליטה ביעדי התקשורת דרך connect-src, ועל היחס ל-unsafe-inline ברשימת מקורות שכוללת nonce או hash. זה הבסיס להערות המעשיות בפרקים 7 ו-10, ואינו מסמך שמראה את ההגדרות של חברת Q. ↩ ↩2
-
WHATWG, XMLHttpRequest Standard — ה-getter וה-setter של withCredentials. על כך שהוא שולט בשאלה האם בקשה cross-origin כוללת credentials, ועל כך שערכו ההתחלתי הוא false. פרק 8 מביא אותו כבסיס לקריאה שאין הגדרה של withCredentials ב-script של השאלה ומכאן ש-cookie לא מצורף. ↩
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
RISS אביב 2024, שאלת PM 1 — JWT alg=none, API authorization ו-WAF
שאלת PM 1 בבחינת RISS של IPA באביב 2024: JWT alg=none, API authorization, Mass Assignment, brute-force על קוד אימות בן 4 ספרות, ו-mitigat...
IPA SC, סתיו 2023, שאלה 2 אחר-הצהריים — קבצים שיוצאים מ-Wi-Fi לאורחים
על בסיס שאלת אחר-הצהריים 2 בסתיו 2023 של בחינת IPA SC (Registered Information Security Specialist), המאמר מסביר איך חברה שחסמה USB עדיין ...
לבנות כלי ניתוח ל-PowerShell בתוך PowerShell — לקרוא סקריפטים עם AST, לא עם ביטויים רגולריים
להציג AST אמיתי של PowerShell ולראות לאיזה אובייקט הופכת כל שורת קוד. לעבור מ-ScriptBlockAst ל-CommandAst ולמצוא היכן נקרא Write-Host, בל...
איך קיצור דרך ב-Windows מוצא קובץ שהעברתם? — המיקום של הקובץ והזהות שלו הם שני דברים שונים
העברתם את הקובץ המקורי, ובכל זאת הקיצור פותח אותו. Windows מחפש את היעד לא רק לפי הנתיב השמור, אלא גם לפי מזהי מעקב ומאפיינים של הקובץ, ו...
האם עדיין צריך "הסרה בטוחה" של כונן USB? — מבט מ"הסרה מהירה" ומטמון הכתיבה
אפשר לשלוף כונן USB ברגע שההעתקה הסתיימה? ההבדל בין מטמון הכתיבה, "הסרה מהירה" ו"ביצועים גבוהים" מסביר מה תפקידה של ההסרה הבטוחה. המאמר כ...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח אתרי אינטרנט
באתר עם חשבונות חברים ופונקציות פרסום, הנקודות במאמר הזה, כמו escaping בזמן הפלט והגדרת תכונות ה-cookie, קובעות ישירות את איכות הבנייה.
ייעוץ טכני וסקירת תכנון
לבדוק, מנקודת המבט של סקירת תכנון, היכן נמצאת אותה חולשה באפליקציית Web קיימת — זו עבודה שנכללת בתחום הייעוץ הטכני.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- למה ה-XSS בשאלה הזו הוא Stored? ה-script מפעיל את ה-DOM, אז זה לא DOM Based XSS?
- סוג החולשה נקבע לפי המקום שבו המחרוזת של התוקף הופכת לקוד שאפשר להריץ, כלומר לפי האם נקודת הפלט הפגיעה (ה-sink) נמצאת בצד השרת או בצד הדפדפן. בשאלה הזו השרת הטמיע את כותרת הביקורת שפרסם התוקף ישירות לתוך ה-HTML והחזיר אותה. ה-sink נמצא בעיבוד הפלט של השרת, והמחרוזת נשמרת ומכאן והלאה נמסרת לכל מי שפותח את העמוד, ולכן זה Stored XSS. DOM Based XSS הוא הקטגוריה שבה JavaScript בדפדפן הופך ערך לניתן להרצה, למשל בהשמה ל-innerHTML. השימוש של ה-script המוטמע ב-DOM API כמו XMLHttpRequest או getElementById לא קשור לסוג החולשה. שימו לב גם לכך שגם אם השרת מחזיר ערך שמור כטקסט תמים או כ-JSON, הוא הופך ל-DOM Based XSS ברגע ש-JavaScript בדפדפן מעביר אותו ל-innerHTML. שפטו לפי היכן הערך הפך לניתן להרצה, ולא לפי האם המחרוזת מופיעה ב-response. גם הערות הציון של IPA מציינות שנבחנים אחדים ענו בטעות DOM Based XSS, כנראה מפני שה-script השתמש ב-DOM.
- לכותרת הביקורת הייתה הגבלת קלט של 50 תווים. איך בכל זאת רץ script ארוך?
- התוקף פיצל את ה-payload לאורכים שנכנסים בפרסום בודד ופרסם אותו בכמה פרסומים. הפרסום הראשון פותח תגית script באמצע הכותרת ופותח הערת בלוק ב-JavaScript (לוכסן וכוכבית) בסוף הכותרת. הפרסום השני סוגר את ההערה בתחילתו, כותב את ההוראה הבאה ופותח הערה נוספת בסופו, וכך חוזר חלילה. בכתיבה כזו כל ה-HTML שנמצא בין פרסום לפרסום (תגיות div וכדומה) נכנס לתוך הערת JavaScript ומדולג, ולכן רק הקטעים המפוצלים מתחברים ורצים כ-script אחד. הגבלת מספר התווים בקלט הגבילה רק את האורך של כל פרסום בנפרד; היא לא הגבילה את מספר הפרסומים.
- לאן נשלח ה-session ID שנגנב?
- הוא לא נשלח לשרת חיצוני. ה-script לקח את תוכן ה-cookie כתוכן של קובץ, נתן לו את השם a.png, הגדיר את ה-MIME type כ-image/png, ושלח אותו לפונקציית העלאת תמונת הפרופיל של אתר ה-e-commerce שאותו הקורבן עצמו משתמש בו. מכיוון שתמונת הפרופיל שמועלית מוצגת בעמוד הביקורות ובמקומות נוספים, התוקף יכול פשוט להוריד את התמונה ולהוציא את ה-session ID מתוך המחרוזת שבתוכה. הדפדפן של הקורבן תקשר רק עם האתר הלגיטימי, ומכיוון שלא נוצרת תעבורה חיצונית חשודה, זהו נתיב שסינון URL ב-proxy וניטור תעבורה יוצאת ב-firewall לא מזהים.
- ההעלאה הייתה אמורה לדרוש token. למה ההתקפה בכל זאת הצליחה?
- מכיוון שה-token הזה הוא מנגנון לדחיית בקשות לא מורשות מאתר אחר (טיפול CSRF), ולא טיפול שנועד ל-script שרץ באותו אתר. ה-script התוקף ניגש תחילה לעמוד הגדרות הפרופיל עם XMLHttpRequest, מקבל את ה-token בדיוק כמו המסך הלגיטימי, ואז מבצע את ההעלאה כשה-token מצורף. מכיוון ש-XSS מריץ את הקוד של התוקף על אותה origin כמו הקורבן, הוא יכול לעשות כל מה שמשתמש לגיטימי יכול לעשות. token של CSRF לא מונע XSS.
- שמעתי שבהזרקת SQL escaping של הקלט הוא שחשוב. האם escaping בזמן הפלט ייחודי ל-XSS?
- לא. גם בהזרקת SQL, escaping בזמן הקלט אינו הטיפול. מה ש-IPA מציגה כפתרון היסודי הוא לממש את כל בניית משפטי ה-SQL עם placeholders. escaping מופיע כחלופה למקרה שבו בונים משפט SQL בשרשור מחרוזות, אבל גם שם מדובר בלבנות נכון את ה-literals של משפט ה-SQL, כלומר זה נעשה ברגע שבונים את משפט ה-SQL, ולא בנקודה שבה הקלט מתקבל. העיקרון המשותף הוא ש-escaping נעשה במקום שבו נקבע לאיזו שפה דקדוקית הנתונים יוצאים. אם הם יוצאים כ-HTML, escaping של HTML; אם הם נעשים חלק ממשפט SQL, placeholders; אם הם נעשים חלק מפקודת shell, הכללים של ה-shell עצמו. הסיבה שאסור לעשות escaping בזמן הקלט היא שבנקודה הזו יעד הפלט עוד לא נקבע. אותם נתונים יוצאים גם לעמודי HTML, גם ל-CSV, גם ל-JSON, גם למייל התראה וגם ל-log, ולכן escaping של HTML בזמן הקלט גורם לכך שהפניות לישויות (entity references) יופיעו כפי שהן ב-CSV, או שמסד הנתונים יחזיק משהו שונה מהקלט המקורי ואז החיפוש והצבירה ייצאו שגויים. זה לא אומר שצד הקלט לא עושה כלום. התפקיד של צד הקלט הוא validation ולא escaping: לדחות ערכים שאינם אפשריים לפי המפרט.
- אילו טיפולים כדאי לקחת מהשאלה הזו חזרה לעבודה?
- הפתרון היסודי הוא לבצע escaping ממש לפני הפלט ל-HTML על כל קלט של משתמש, כולל כותרת הביקורת. מעבר לכך, אמצעי ההגנה המשניים הם להגדיר את התכונה HttpOnly על ה-cookie של ה-session ID כדי ש-scripts לא יוכלו לקרוא אותו, לבצע re-encode לתמונות שמועלות בצד השרת כדי שרצף הבתים המקורי לא יישמר, ולהגדיר Content Security Policy שיכול לעצור הרצה של script inline. אילו חברת Q בשאלה הזו הייתה מיישמת אפילו אחד מאלה, שרשרת התקיפה הייתה נשברת במקום כלשהו. אבל אמצעי הגנה משניים אפשר לעקוף אם התוקף משנה את השיטה, ולכן הם לא מחליפים את ה-escaping, שהוא הפתרון היסודי.