בקשת HTTP אחת, שדה אחד בשם command, ובאמצע אין כלום. אין מסך התחברות, אין בדיקת session, אין בדיקה מאיזה אתר הבקשה הגיעה. מה שנכתב בשדה הזה מפורסר ונמסר הלאה לאותו ערוץ שדרכו מפעיל אנושי שולח פקודות למכשור ולחלליות. "אין שום דבר בין הרשת לבין ה-command bus", כתב על זה החוקר.

זו התמצית של הניתוח שפרסם ב-18 באוגוסט יובל אלבר, חוקר אבטחה ב-Cycode. הנושא הוא AIT-GUI, קונסולת ההפעלה מבוססת הדפדפן של ערכת הכלים AMMOS Instrument Toolkit של NASA/JPL. חמישה ימים קודם לכן פרסם הפרויקט התרעת אבטחה משלו, שנותנת לבעיה ציון CVSS של 9.4 ומסמנת כחשופות את כל הגרסאות עד 2.5.1 כולל. לפי Cycode, בזמן כתיבת הפוסט עדיין לא הוקצה לחולשה מזהה CVE. בהמשך נעבור על ארבע הטעויות שמצטרפות כאן לשרשרת אחת, על הסיבה שגם התקנה מאחורי חומת אש לא סגורה, ועל הדרך שבה הדברים התגלו מלכתחילה.

מה יושב מאחורי הקונסולה הזאת

ה-AMMOS Instrument Toolkit, בקיצור AIT, הוא חבילת תוכנה בקוד פתוח שכתובה בפייתון ומפותחת ב-NASA/JPL. התיעוד שלה מתאר מערכת שמטפלת בתחנת הקרקע, בציוד התמיכה הקרקעי, בשליחת פקודות, בקליטת טלמטריה ובניהול רצפי פעולות, עבור משימות מכשור ומשימות קיוב-סאט, והיא נולדה כהכללה של כלים שפותחו לכמה משימות בתחנת החלל הבינלאומית. AIT-GUI הוא הרובד שמעליה: ספרייה לבניית אתר שדרכו מפעיל רואה טלמטריה בזמן אמת ושולח פקודות, ומאחורי האתר REST API שמחזיק את כל הפעולות המעניינות.

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

שרשרת של ארבע טעויות

הראשונה היא הגדרה שנקראת ואז מושלכת. השרת קורא את ערך ה-host מקובץ ההגדרות, ומיד אחר כך עולה על 0.0.0.0 בכל מקרה, כלומר על כל ממשקי הרשת של המכונה. התוצאה הפוכה בדיוק מהכוונה של מי שהגדיר אותו: לפי Cycode, מפעיל שהקפיד לרשום host: localhost כדי להשאיר את הקונסולה על הלולאה המקומית מקבל בכל זאת שרת שחשוף לכל הרשת שאפשר להגיע ממנה. פורט ברירת המחדל הוא 8080.

השנייה היא היעדר אימות בכלל. שלושת ה-endpoints שמשנים מצב, /cmd לשליחת פקודה, /seq להרצת רצף ו-/script/run להרצת סקריפט בצד השרת, לא עוברים בשום שלב במסלול הקריאה שלהם דרך בדיקת הרשאה, בדיקת session או middleware כלשהו. בפועל, מה שמגיע בשדה command נמסר ישירות לפונקציית השליחה.

השלישית נוגעת לשני ה-endpoints שמקבלים שם קובץ. גם /seq וגם /script/run מרכיבים נתיב מתוך הקלט של המשתמש בלי לוודא שהנתיב הסופי נשאר בתוך התיקייה המיועדת, כך שערך שמתחיל בסדרה של חזרות אחורה בעץ התיקיות מוציא את הקריאה מחוץ לשורש שהוגדר. לטכניקה הזאת קוראים Path Traversal. הפרט שהופך את זה למביך הוא שבדיקת הכליאה הנכונה כבר קיימת באותו קובץ עצמו, על endpoint שכן שנקרא scripts/load, ושני האחרים פשוט לא משתמשים בה. הרביעית היא הצורה שבה הבקשות האלה מתקבלות, וזה כבר סיפור נפרד.

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

אז למה חומת אש לא סוגרת את הפינה?

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

בדרך כלל יש כאן בלם. דפדפן ששולח בקשה לשרת אחר שולח לפניה בקשת בדיקה מקדימה ומחכה לאישור מהשרת. הבלם הזה לא עובד כאן, כי ה-endpoints של AIT-GUI מקבלים תוכן בקידוד טופס רגיל, וזה בדיוק סוג הבקשה שהדפדפן מסווג כפשוטה ושולח בלי לבדוק קודם. "גם התקנה מקומית או כזאת שמוגנת בחומת אש ניתנת לניצול, כי אפשר להגיע ל-endpoints מדפדפן וממקור זר", נכתב בניתוח. אלבר מדגים את זה בעמוד בן כמה שורות ששולח פקודת NO_OP, פקודה שלא עושה כלום, כדי להראות שהמסלול פתוח בלי לגעת בפקודה אמיתית.

הרשת סגורה. הדפדפן לא.

איך בכלל מוצאים דבר כזה

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

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

זו הפעם השנייה שאותו endpoint מקבל תיקון

ב-10 ביולי פרסם הפרויקט התרעה קודמת על אותה תוכנה, CVE-2026-77309, בציון 8.8, שנגעה לגרסאות 1.0.0 עד 2.5.0 ותוקנה ב-2.5.1. גם שם הכוכב הראשי הוא /script/run. השרשרת שם מורכבת משני שלבים: ה-endpoint שאחראי על שאילתות טלמטריה כותב קלט שמגיע מהמשתמש לקובץ זמני בתיקיית הקבצים הסטטיים, וכשאין קובצי pcap בתיקיית הנתונים הוא מחזיר שגיאה 400 ויוצא עוד לפני שניקה את הקובץ. הקובץ נשאר שם. ואז /script/run, שכאמור לא מוודא שהנתיב שקיבל נשאר בתוך התיקייה שלו, מקבל נתיב יחסי שמחזיר אותו לאותו קובץ ומריץ את תוכנו כקוד פייתון בתוך תהליך השרת. את השרשרת ההיא דיווח חוקר בכינוי Rat5ak, ואת החולשה החדשה מזכה ההתרעה לאלבר. (וזה, אגב, אותו endpoint בדיוק שקיבל שני תיקונים בתוך פחות מחמישה שבועות.)

מה זה אומר בפועל

מי שמריץ AIT-GUI צריך לעלות ל-2.5.2, ופה מחכה פרט שכדאי לשים לב אליו: ההתרעה מסמנת את החבילה כפי שהיא מותקנת ב-pip, אבל במדד PyPI הגרסה העדכנית ביותר היא 2.4.1 מיולי 2023, וסדרת 2.5 מופיעה רק במאגר הקוד של הפרויקט. כלומר התקנה רגילה דרך pip לא תביא לכם את התיקון, וצריך למשוך אותו מהמאגר. אחרי זה כדאי לוודא שהפורט לא נגיש מרשתות שאתם לא סומכים עליהן, ומי שהשאיר אותו פתוח בעבר שיעבור על היסטוריית הפקודות והרצפים ויחפש שם מה שהוא לא שלח בעצמו. ובעיקר, מי שמפעיל קונסולה מהסוג הזה כדאי שיזכור שהדפדפן שפתוח לידה הוא חלק ממשטח התקיפה שלה.