פרק ב - מבאג לאקספלויט
(אקספלויט = תוכנה שמנצלת חולשה)
תקציר
בפרק הקודם ראינו איך שאפשר לנצל באג בפקודת pkexec
כדי לגרום לזרימה לא צפויה של הקוד ולשלוט על אחת ממשתני הסביבה שלה. אבל עדיין לא הסברנו איך נוכל לנצל עובדה זו כדי להריץ קוד אקראי כרוט.
רק נחדד, תוכנת pkexec
לא מאפשרת הרצת פקודות אקראיות כרוט בלי להעביר מבחן, המבחן יכול להיות תיבת דו-שיח שמחייבת הזנת סיסמת מנהל, או זה יכול להיות בדיקה של הרשאה מיוחדת בקובץ תצורה במקום מוגדר במערכת הקבצים (הגישה לקובץ תצורה זו ניתנת רק לרוט). המטרה שלנו הוא לאפשר לפורץ להריץ את הקוד שלו עם הרשאות גבוהות בלי צורך להזין את סיסמת המנהל, ובלי צורך לרשיון מיוחד שהוגדר על ידי המנהל.
בינתיים קיבלנו את האפשרות לגרום לתוכנה לפענח את משתנה הסביבה הראשונה כשם של פקודה, ובנוסף התוכנה מחפשת את הפקודה הזו ב-PATH
ומכניסה חזרה למשתני הסביבה את הנתיב לפקודה.
אבל עדיין פקודה זו לא תעבור את המבחן.
אז איך ננצל את החולשה כדי להריץ קוד אקראי שלנו?
(אגב, יש מישהו שטוען שהוא עלה על הבאג כבר בשנת 2013, אבל לא מצא את הדרך איך לנצל אותו. עיין כאן העתק של מייל שהוא כתב לפרוייקט polkit בנושא, לא ידוע למה התעלמו ממנו)
משתני סביבה מסוכנים
התשובה: רמזנו כבר למעלה, יש משתני סביבה "מסוכנים" שמערכת ההפעלה מנקה לפני הרצת תוכנה עם setuid. אם נוכל להזריק שוב לתוך הסביבה משתנה מסוכנת, נוכל לנצל אותה כדי להריץ קוד אקראי.
(במקרה של pkexec
יש רק חלון הזדמנות קטן לנצל את זה, כי די קרוב למקום הבאג, התוכנה עצמה עושה שוב נקיון משתני הסביבה. למרבה המזל (?) עדיין מצאו אפשרות ניצול בתוך קטע הקוד הקצר עד לנקיון החוזר)
GCONV_PATH
הרכיב שאחראי על טעינת תוכנות לזכרון והכנתם להרצה בלינוקס הוא ld.so
, בתיעוד של הרכיב (man ld.so
או כאן) ניתן למצוא רשימה של משתני סביבה מסוכנים שהרכיב מנקה לפני הרצת תוכנות עם setuid. בתוכם נמצא את המשתנה GCONV_PATH
.
המשתנה GCONV_PATH
מוכרת על ידי ספריית iconv
. ספרייה זו נמצאת ברוב ככל מערכות לינוקס (זה חלק מתקן POSIX שמגדיר מה אפשר לצפות ממערכת UNIX-ית) והיא אחראית על המרת טקסט בין קידודים שונים. כאשר תוכנה כלשהי קוראת לפונקציה מתוך ספריית iconv לבקש המרת טקסט לקידוד כלשהו, הספרייה בודקת את משתנה הסביבה GCONV_PATH
כדי למצוא את הנתיב לקובץ תצורה. בתוך קובץ זה ניתן להגדיר נתיבים לספרייות קוד שנטענות בצורה דינאמית כדי לבצע את ההמרה. (ע"ע man iconv
או כאן)
g_printerr
ומשתנה הסביבה CHARSET
בקטע הקצר של הקוד מהבאג ועד לניקיון חוזר של משתני הסביבה יש קריאה לפונקציה g_printerr
. פונקציה זו היא חלק מספריית glib (ספריית היסוד של GTK ש-polkit הוא חלק ממנה). פונקציה זו (כשמה) מדפיסה שגיאות לקונסול, והיא מכבדת את משתנה הסביבה CHARSET
כדי לבחור קידוד להדפסה זו. שינוי הקידוד מהמחרוזת המקורית לקידוד הרצוי מתבצעת על ידי... iconv.
והנה קטע הקוד (הנוגע לענינינו) שמדפיס את הודעת השגיאה:
383 validate_environment_variable (const gchar *key,
384 const gchar *value)
385 {
...
406 log_message (LOG_CRIT, TRUE,
407 "The value for the SHELL variable was not found the /etc/shells file");
408 g_printerr ("\n"
409 "This incident has been reported.\n");
מה שחשוב להבין היא שהעובדה שמשתנה הסביבה SHELL
מצביעה על shell לא חוקי תגרום להדפסת השגיאה, ולקריאת פונקציית iconv.
עכשיו הדרך לאקספלויט מלא פתוחה לפנינו...
עוד לא הבנת? זה בסדר, זה מצריך קצת יצירתיות. תעקוב אחרי:
האקספלויט
בתור הכנה, נעשה את השלבים הבאים (אח"כ אנסה להסביר):
- נבחר תיקייה כלשהו כ"תיקיית הבית" של האקספלויט.
- ניצור תיקייה (בתוך "תקיית הבית" שלנו) בשם
GCONV_PATH=.
(כן, ה-=
וה-.
הם חלק מהשם) - בתוך תיקייה זו נשים קובץ EXE כלשהו (כלומר, קובץ שיש לו הרשאות ריצה). לא משנה איזה. נקרא לקובץ שם כלשהו, לצורך הענין נבחר בשם "xxx" ונוסיף את התווים
:.
בסוף השם. השם המלא יוצא:xxx:.
(ההסבר יבוא) - ניצור קובץ קינפוג ל-iconv ב"תיקיית הבית" שלנו. בתוך הקובץ יש שורה אחת שאומרת ל-iconv שכדי להמיר קידוד UTF-8 לקידוד (שהמצאנו לצורך הענין) "PWNKIT" צריך לטעון את הקוד של ספרייה בשם "pwnkit.so". (השורה נראית משהו כזה:
module UTF-8// PWNKIT// pwnkit
. לעוד פרטים עיין בתיעוד). נקרא לקובץ בשם iconv_modules - ניצור קובץ בינארי (ספרייה - מה שנקרא DLL בעולם הווידוס) בתוך "תקיית הבית" שלנו, ונקרא אותו בשם pwnkit.so. הספרייה הזו מתחזה כמודול של iconv (ז.א. הוא מממש את הפונקציות
gconv_init
ו-gconv
) בתוך הקוד של פונקצייתgconv_init
נוכל להריץ קוד אקראי עם הראשות רוט... - ניצור עוד תוכנה שמריצה את pkexec עם פרמטרים מיוחדים:
- argv של אבר אחד שהוא null pointer.
- envp שכולל מספר ערכים:
- הראשון:
xxx:.
(כן, זה לא נראה כמשתנה סביבה. נראה בהמשך מה יקרה עם זה) PATH=GCONV_PATH=.
SHELL=/i/do/not/exist
CHARSET=PWNKIT
- הראשון:
טוב, סיימנו את ההכנות, נצא לדרך!
נעקוב אחרי זרימת הקוד ואני מקוה שתוכלו להבין איך האקספלויט עובד
- נריץ את התוכנה שיצרנו בשלב האחרון של ההכנות
- זה מריץ את הקוד של pkexec
- כפי בתיארנו בפוסט הראשון, pkexec יטעה, ויחשוב שמשתנה הסביבה הראשונה היא היא שם הפקודה שהעברנו אליה
- היא מעבירה את זה לפוקנציה שמחפשת פקודה זו ב-PATH ומחזירה את הנתיב לפקודה (שורה 632 בקטע הקוד בפוסט הראשון)
- מכיון שה-PATH שלנו מכילה ערך אחד:
GCONV_PATH=.
, הפוקנציה תחפש תיקייה בשםGCONV_PATH=.
ובתוכה קובץ בשם:xxx:.
- הפונקציה תמצא את הקובץ שהכנו, והיא תחזיר לתוכנה את הנתיב אליה:
GCONV_PATH=./xxx:.
- pkexec תיקח תוצאה זו ותכניס אותה חזרה ל... משתנה הסביבה הראשונה. יוצא שהצלחנו להזריק משתנה הסביבה לא בטוחה לתוך התהליך. משתנה הסביבה נראית כך עכשיו:
GCONV_PATH=./xxx:.
- התוכנה תמשיך לרוץ ומכיון שהגדרנו משתנה
SHELL
לא חוקית, נגיע להודעת השגיאה... - פונקציית ההדפסה מכבדת את משתנה הסביבה
CHARSET
שערכו אצלנו "PWNKIT", לכן היא מנסה להמיר את טקסט ה-UTF-8 שבקוד המקור, לקידוד "PWNKIT" - כדי לבצע את ההמרה היא מחפשת את הגדרות iconv לפי משתנה הסביבה
GCONV_PATH
. משתנה זו אמורה להכיל רשימה של נתיבים מופרדים באמצעות תו:
. אצלנו יש שני ערכים ברשימה:./xxx
ו-.
. - הפונקציה מחפשת בשני הנתיבים לקובץ בשם iconv_modules. הנתיב הראשון לא קיים כמובן, אבל השני קיים. ה-
.
מסמל את התיקייה הנוכחית. בינגו! יש שם קובץ תצורה. בוא נראה מה כתוב: - הופ! כתוב שלהמיר קידוד UTF-8 לקידוד PWNKIT אני צריך לטעון את הספרייה בשם pwnkit, אז בוא נחפש ספרייה בשם זה בתקייה הנוחכית...
אתם יכולים לנחש את הסוף העצוב של הסיפור...
(קרדיט: נעזרתי בשני מקורות אלו להכנת המאמר: