מה זה SSTI - הזרקת תבניות, ולמה זו אחת החולשות המפחידות בעולם הווב¶
אם עשיתם כבר כמה אתגרי ווב, אתם בטח מכירים XSS - להזריק קוד שרץ אצל הקורבן בדפדפן. אבל יש חולשה אחרת, פחות מוכרת ומרגישה, שבה במקום להריץ קוד אצל מישהו אחר, אתם גורמים לשרת עצמו להריץ את הקוד שלכם. זה נקרא SSTI, וברגע שמבינים אותה, קשה לא להתאהב בה.
אני כותב את זה כי SSTI היא בדיוק סוג החולשה שממחישה למה כדאי להבין מה קורה "מתחת למכסה המנוע" של אפליקציות ווב, ולא רק לשנן רשימת פייאלודים.
אז מה זה בעצם מנוע תבניות¶
לפני שנדבר על החולשה, צריך להבין מה בכלל מנוע תבנית - template engine. כמעט כל אתר מודרני לא בונה HTML ידנית בקוד. הוא כותב קובץ תבנית עם מקומות ריקים, ומזריק לתוכם ערכים. למשל תבנית שכתובה כך:
השרת לוקח את השם שהמשתמש הזין, מכניס אותו במקום {{ name }}, ושולח את התוצאה בחזרה. מנועים כמו Jinja2 בפייתון, Twig ב-PHP או Freemarker בג'אווה עושים בדיוק את זה, כל אחד עם התחביר שלו.
הבעיה מתחילה כשמפתח, מתוך נוחות או חוסר תשומת לב, לוקח קלט של המשתמש ומכניס אותו ישירות לתוך התבנית עצמה, ולא רק כערך שמוצב בתוכה. במקום להעביר את name כמשתנה, הוא בונה את מחרוזת התבנית עצמה עם קלט המשתמש בפנים, ואז מריץ אותה דרך המנוע.
למה זה כל כך חמור¶
וכאן נכנס ההבדל המהותי בין SSTI ל-XSS. ב-XSS, אתם מזריקים קוד JavaScript שרץ בדפדפן של הקורבן - זה חמור, אבל הוא מוגבל לסביבת הדפדפן, לעוגיות, להרשאות של אותו משתמש בדפדפן שלו.
ב-SSTI, אתם מזריקים תחביר של מנוע התבניות עצמו, וזה רץ בצד השרת, בהרשאות של האפליקציה. מנועי תבניות רבים לא נבנו רק כדי להציב טקסט - הם מאפשרים לוגיקה: תנאים, לולאות, גישה לאובייקטים פנימיים, ולפעמים גם קריאה לפונקציות פייתון או ג'אווה אמיתיות. אם תוקף מצליח להזריק תחביר שהמנוע מפרש, הוא לא שולט בדף אחד אצל קורבן אחד - הוא עלול לשלוט בשרת עצמו.
- XSS - קוד רץ בצד הלקוח, בדפדפן של הקורבן.
- SSTI - קוד רץ בצד השרת, בהרשאות של האפליקציה עצמה.
זה ההבדל בין לגנוב עוגיית סשן אחת, לבין להשיג הרצת קוד מרחוק - RCE - על השרת המרכזי.
איך זה נראה בפועל¶
הדרך הקלאסית לבדוק אם יש SSTI היא להזריק ביטוי מתמטי במקום טקסט רגיל, ולראות אם השרת "פותר" אותו. אם אתם שולחים כשם משתמש משהו כמו {{7*7}}, ובתגובה מקבלים "שלום 49" במקום "שלום {{7*7}}", זה סימן ברור שהקלט שלכם לא הוצג כטקסט - הוא הורץ כתחביר תבנית.
מרגע שיש לכם את האישור הזה, המשחק האמיתי מתחיל. בהתאם למנוע התבניות ולשפה שמאחוריו, אפשר לנסות לנווט מתוך התבנית לאובייקטים פנימיים של השפה, ומשם - בתרחיש הגרוע ביותר - להגיע לפונקציות שמריצות פקודות מערכת. זו הסיבה ש-SSTI מסווגת כמעט תמיד כחולשה קריטית, ולא כתקלה קוסמטית.
למה זה קורה בכלל¶
ברוב המקרים זו לא רשלנות מכוונת, אלא נוחות. מפתחים רבים משתמשים באותם מנועי תבניות גם ליצירת מיילים דינמיים, דוחות מותאמים אישית, או אפילו הודעות שגיאה שמתבססות על קלט משתמש - ובונים את מחרוזת התבנית "תוך כדי תנועה" עם נתונים שהגיעו מבחוץ. זה בדיוק אותו סוג טעות שראינו כבר ב-SQL Injection - לערבב קוד עם דאטה - רק שהפעם זה קורה בתוך מנוע תבניות במקום במסד נתונים.
הפתרון, כמו תמיד, הוא הפרדה ברורה - קלט משתמש תמיד צריך להיכנס כערך שמוצב בתוך תבנית קבועה, ולעולם לא כחלק מהתבנית עצמה.
מזהים חולשת SSTI זו לא סוף הסיפור¶
זיהוי החולשה זה רק הצעד הראשון. אחרי שיש לכם אישור שהקלט שלכם מתפרש, יש עבודה אמיתית - לזהות איזה מנוע תבניות רץ מאחורי הקלעים, למצוא את המסלול המדויק מתוך התבנית לפונקציות מסוכנות בשפה, ולבנות פייאלוד שעובד בסביבה הספציפית הזו. זה בדיוק אחד הנושאים שאנחנו מפרקים לגורמים, שלב אחר שלב, בקורס פריצת אתרים מתקדם, כי בלי להבין את הפנימיות של המנוע קשה מאוד להתקדם מ"מצאתי חולשה" ל"הוכחתי השפעה אמיתית".
ובדרך הזו כדאי שלא תהיו לבד. בדיסקורד שלנו יש אנשים שפותרים אתגרי SSTI ממש בימים אלה ותמיד שמחים לעזור כשמישהו נתקע.
לסיכום¶
SSTI מתחילה כמעט תמיד באותה טעות פשוטה - קלט משתמש שמתערבב עם קוד תבנית במקום להישאר ערך נפרד - אבל היא יכולה להסתיים בשליטה מלאה על השרת. זו דוגמה מצוינת לכך שהחולשות המסוכנות ביותר לא תמיד מסובכות להבין, הן פשוט קלות מדי להחמיץ.