מה זה חולשת מחרוזת פורמט, ולמה printf יכול להיות מסוכן¶
יש חולשות שנולדות מטעויות מסובכות, ויש חולשות שנולדות מהרגל תכנותי רשלני לגמרי. חולשת מחרוזת פורמט שייכת בבירור לקטגוריה השנייה - וזו בדיוק הסיבה שהיא כל כך מרתקת ללמוד. שורת קוד אחת, קטנה וחפה מפשע למראה, יכולה לפתוח דלת רחבה לתוך הזיכרון של התוכנית.
מה זו בכלל "מחרוזת פורמט"¶
בשפת C, פונקציות כמו printf מקבלות מה שנקרא "מחרוזת פורמט" - טקסט שמכיל סימוני מקום (placeholders) כמו %d למספר, %s למחרוזת, ו-%x למספר הקסדצימלי. לכל סימון מקום כזה, הפונקציה מצפה לקבל ארגומנט נוסף שמתאים אליו. לדוגמה, printf("שלום %s, יש לך %d הודעות", name, count) - שני סימוני מקום, שני ארגומנטים.
הבעיה מתחילה כשמתכנת כותב קוד כמו printf(user_input) במקום printf("%s", user_input). ההבדל נראה זעיר, אבל הוא עצום. בגרסה הראשונה, כל מה שהמשתמש מזין נחשב למחרוזת הפורמט עצמה. אם מישהו יזין %x %x %x %x, הפונקציה תנסה "למלא" את סימוני המקום האלה - אבל מכיוון שאף אחד לא סיפק לה ארגומנטים תואמים, היא פשוט תמשוך נתונים מהסטאק, אחד אחרי השני, בלי לדעת שהם לא שייכים לקריאה הזאת בכלל.
מה אפשר לעשות עם זה בפועל¶
זה נשמע כמו באג קטן, אבל היכולות שהוא פותח משמעותיות מאוד:
- דליפת מידע - Information Leak. עם
%xשוב ושוב, אפשר "לקרוא" ערכים מהסטאק - כתובות זיכרון, ערכים של משתנים, ולפעמים אפילו מידע רגיש כמו סיסמאות ששוכבות שם במקרה. זה שימושי במיוחד כדי לעקוף הגנות כמו ASLR, כי אפשר לחשוף כתובות אמיתיות בזיכרון. - קריאת זיכרון שרירותית. באמצעות
%sבשילוב עם כתובת שכבר נמצאת על הסטאק, אפשר לגרום לפונקציה "לקרוא" את התוכן שנמצא בכתובת הזאת ולהדפיס אותו. - כתיבה לזיכרון - וכאן זה נהיה באמת מסוכן. יש סימון פורמט פחות מוכר בשם
%n, שבמקום להדפיס ערך, כותב לתוך הזיכרון את מספר התווים שהודפסו עד כה. תוקף שמבין איך לשלוט על הערך הזה יכול להשתמש בו כדי לכתוב כמעט כל ערך לכל כתובת בזיכרון - כולל, למשל, דריסה של כתובת החזרה.
זה בדיוק מה שהופך חולשת מחרוזת פורמט למסוכנת יותר ממה שהיא נראית במבט ראשון - מחולשה שמדליפה מידע היא יכולה להפוך לחולשת הרצת קוד מלאה.
למה זה עדיין קורה¶
אפשר לחשוב שזה באג כל כך פשוט למניעה שהוא נכחד מזמן. אבל בפועל, מתכנתים עדיין נופלים בזה, בעיקר כשהם משתמשים במשתנה משתמש ישירות כמחרוזת פורמט מתוך נוחות או חוסר תשומת לב. קומפיילרים מודרניים כן מזהירים על זה (אזהרות כמו -Wformat-security), אבל אזהרה זה לא אכיפה, ואם אף אחד לא קורא את הפלט של הקומפיילר, החולשה עוברת ישר לפרודקשן.
איך מזהים חולשה כזאת¶
הסימן הכי ברור הוא לחפש בקוד קריאות לפונקציות מהמשפחה של printf (וגם fprintf, sprintf, syslog ואחרות) שבהן הארגומנט הראשון הוא ישירות קלט משתמש, בלי %s מפורש שעוטף אותו. אם אתם עושים ניתוח דינמי של תוכנה, אפשר גם פשוט לנסות להזין %x%x%x%x או %n לכל שדה קלט ולראות אם התוכנית מתנהגת מוזר או קורסת.
איך זה מתחבר לשאר עולם הניצול הבינארי¶
חולשת מחרוזת פורמט היא דוגמה מצוינת לכך שלא כל חולשה חייבת להתחיל מגלישת חוצץ קלאסית. לפעמים הבאג נמצא בשימוש שגוי בפונקציה תמימה, לא בחריגה ישירה מגבולות זיכרון. זו הסיבה שהיא נלמדת בדרך כלל מיד אחרי גלישת חוצץ - היא מלמדת אתכם לחשוב על חולשות מזווית שונה לגמרי.
בקורס מחקר חולשות אנחנו עוברים על חולשת מחרוזת הפורמט עם תרגול מעשי - איך מזהים אותה בקוד, ואיך משתמשים בה גם לדליפת מידע וגם לכתיבה שמובילה להרצת קוד.
נתקעתם על אתגר עם מחרוזת פורמט? יש לנו קהילה שלמה בדיסקורד שאוהבת בדיוק את סוג החידות האלה.
לסיכום¶
חולשת מחרוזת פורמט מוכיחה שלפעמים החולשות הכי מסוכנות לא נובעות ממתקפה מתוחכמת, אלא מהרגל תכנותי רשלני. שורת קוד אחת שמחליטה להעביר קלט משתמש ישירות ל-printf יכולה לפתוח דלת מדליפת מידע ועד להרצת קוד מלאה. זו בדיוק הסיבה שכדאי להבין אותה לעומק, גם אם היא נראית "פשוטה" ביחס לחולשות אחרות בתחום.