המוצר שחסם את התוקף פעמיים הפך לדרך ההפצה שלו: עדכון מזויף של cPGuard רץ כ-root על כ-2,950 שרתי אירוח
ב-1 בנובמבר, ב-21:40, cPGuard חסם כתובת IP שהתנהגה בצורה חשודה מול אחד השרתים שהוא מגן עליהם. אחת-עשרה דקות אחר כך הוא חסם אותה שוב. הכתובת הזאת, 94.183.188.91, הייתה של מי שבדיוק באותן דקות התקין את cPGuard על שרת פרטי שלו, כדי להבין איך מערכת הרישוי של המוצר עובדת. חמש שעות אחר כך הוא העלה עדכון מזויף לערוץ ההפצה של אותו מוצר בדיוק, והעדכון רץ בהרשאות root על כ-2,950 שרתי לקוחות ואסף מהם את הסודות.
cPGuard הוא חבילת אבטחה לשרתי Linux של אירוח אתרים, סריקת נוזקות וחומת אש שיושבות לצד לוחות ניהול כמו cPanel, DirectAdmin ו-Plesk. המוצר נמכר גם דרך ספקי אירוח, ו-Namecheap למשל מציעה אותו ללקוחות VPS ושרתים דדיקייטד. היצרנית OPSSHIELD פרסמה ב-3 בנובמבר שתי הודעות: התראה על ערוץ העדכונים ודיווח מלא על התקרית שכולל ציר זמן מדויק. שתיהן הדיווח של החברה על עצמה, ואף גורם חיצוני לא אימת אותן. מה שהן מתארות הוא מקרה נקי במיוחד של מתקפת שרשרת אספקה: לא חולשה בקוד שהופצה ללקוחות, אלא השתלטות על התשתית שמחליטה מה בכלל נחשב עדכון לגיטימי. נעבור על הדרך שהתוקף עשה, על שבעים הדקות שבהן העדכון המזויף התקין את עצמו, ועל מה שנשאר אחריו.
הכול התחיל בהרשמה רגילה לגרסת ניסיון
ציר הזמן בדיווח מתחיל ב-1 בנובמבר ב-15:38, כשהתוקף התחיל לתשאל את פורטל החיוב של החברה. ב-16:34 הוא נרשם שם כלקוח תחת זהות כזב והזמין גרסת ניסיון חינמית. ב-21:17 הוא התקין את הגרסה הזאת על שרת שבשליטתו.
השלב הזה שווה עצירה, כי הוא מסביר את כל מה שבא אחריו. סוכן אבטחה שמותקן על שרת לקוח צריך לדעת שהרישיון שלו תקף, ולכן הוא מדבר עם שרת רישוי אצל היצרן. אותו שרת רישוי הוא גם מי שמגיש את העדכונים. תוקף שמתקין את המוצר אצלו בבית רואה את כל הדיאלוג הזה מהצד של הלקוח: לאיזו כתובת הסוכן פונה, מה הוא שולח, ומה הוא מצפה לקבל בחזרה. זה מודיעין, והוא נמסר בחינם לכל מי שממלא טופס הרשמה.
ובדיוק אז המוצר עבד. ב-21:40 וב-21:51 cPGuard זיהה את הפעילות של התוקף וחסם אותו, פעמיים. החסימות היו זמניות, ובדיווח OPSSHIELD כותבת שהיא מתקנת כרגע את אורכן בחומות אש מסוג nftables.
מהפורטל אל המפתח
ב-22:50 התוקף שלח בקשות מעובדות לעמודי callback של פורטל החיוב, אותם עמודים שתפקידם לקבל הודעות אוטומטיות מספקי תשלום. שמונה דקות אחר כך, ב-22:58, הייתה לו הרצת קוד על השרת שמריץ את החיוב והרישוי, והוא התחיל לאסוף ממנו מידע.
איך בדיוק זה קרה OPSSHIELD לא יודעת לומר. בדיווח היא כותבת שהלוגים שלה לא שמרו את הבקשה המדויקת שפתחה את הדלת, ושבדיקת שורש הבעיה עוד נמשכת. מה שהיא כן אומרת זה מה שפסלה: לא SSH, לא לוח הניהול, לא FTP ולא token-ים של API. הכניסה הייתה דרך אפליקציית החיוב עצמה, עם החשבון שהתוקף פתח לעצמו שעות קודם.
על אותו שרת שכב מפתח גישה שמשמש להעלאת עדכונים לרשת ההפצה. ברגע שהמפתח הזה בידי התוקף, למערכת שבודקת עדכונים אצל הלקוח אין שום דרך לדעת שמשהו השתנה: המקור הוא המקור הנכון, הכתובת היא הכתובת הנכונה, והחבילה חתומה כמו שצריך. זה המקום שבו מתקפת שרשרת אספקה שונה מפריצה רגילה. אין מה לזהות.
שבעים הדקות
בין 02:04 ל-02:09 ב-2 בנובמבר התוקף העלה את העדכון המזויף. הסוכנים אצל הלקוחות התחילו להתקין אותו ב-02:08, וההפצה נמשכה עד 03:20, חלון של כשבעים דקות, כלשון הדיווח. הגרסאות שנשאו את הקוד הזדוני הן 5.88.12 עד 5.88.19.
הסקריפט שהתקין את עצמו רץ בהרשאות root, כי ככה סוכן אבטחה על שרת עובד. מה שהוא אסף מופיע בהתראה אחד לאחד: קובצי /etc/shadow ו-/etc/passwd, כלומר גיבובי הסיסמאות של כל המשתמשים המקומיים; קובץ ההגדרות my.cnf של root ב-MariaDB או MySQL, שבדרך כלל מכיל את סיסמת מסד הנתונים בטקסט גלוי; היסטוריית הפקודות של root; וקובצי ה-SSH של root, גם authorized_keys וגם known_hosts. על חלק מהשרתים נאספו גם מפתחות של אחסון S3.
היסטוריית הפקודות היא הפריט שקל לפספס ברשימה הזאת. כל מי שאי פעם הדביק לשורת הפקודה token של API, סיסמת מסד נתונים או מפתח של ספק ענן יודע בדיוק איפה הדבר הזה נשמר. known_hosts הוא פריט נוסף מאותו סוג: הוא לא סוד בעצמו, אבל הוא רשימה מסודרת של כל השרתים שמהמכונה הזאת מתחברים אליהם.
את מה שאסף הסקריפט הוא העלה חזרה לשרת הרישוי של OPSSHIELD, דרך אותו חשבון פרוץ, ומשם התוקף שלף את התיקים. ההתראה מדווחת על 32 קבצים שנשלפו בפועל, אחד לכל שרת לקוח, ומדגישה שזו הערכה מקדימה של שליפה מאומתת ולא מספר השרתים שנפגעו. בין שני המספרים הפער גדול: כ-2,950 שרתים הריצו את העדכון, ו-32 תיקים אומתו כנשלפו. OPSSHIELD בכל זאת מורה להתייחס לכל שרת שהיה מחובר בחלון הזמן הזה כאילו הסודות ברמת root שלו ידועים לתוקף.
ההתראה מפרטת גם את העקבות שנשארו על הדיסק: הסקריפט עצמו בשם cpguard-v58812-i91511.sh, תיקיית איסוף ב-/var/tmp/.cpg91511 וארכיון בשם loot.tgz בתוכה, shell נסתר ב-/var/tmp/.cpg91511_sh ומשימה מתוזמנת ב-/etc/cron.d/i93677. היעדר הקבצים האלה לא מוכיח שהסקריפט לא רץ, כי העדכון הנקי מוחק אותם בעצמו.
ב-02:13, חמש דקות אחרי שההפצה התחילה, התוקף העתיק שלושה שרתי ענן של החברה עצמה. על השרת הפנימי נמצאו גם רכיבי שימור אחיזה: backdoor מסוג gsocket ו-implants שנכתבו ב-Go ומסתירים את עצמם מכלי המערכת הרגילים.
ומה עם פורטל החיוב?
הצד השני של התקרית הוא המידע של הלקוחות עצמם. לפי הדיווח נלקחו מפורטל החיוב פרטים של כ-7,900 חשבונות: שמות, שמות חברה, כתובות למשלוח דואר, כתובות דואר אלקטרוני וגיבובי סיסמאות. OPSSHIELD מציינת במפורש שסיסמאות הפורטל נשמרו כגיבובי bcrypt ולא בטקסט גלוי, כלומר אי אפשר לקרוא אותן ישירות. סיסמה חלשה או סיסמה שחוזרת באתר אחר עדיין בסיכון.
מעבר לרשימה הזאת, החברה התחילה להנפיק מחדש את כל זוגות מפתחות הרישוי של cPGuard, כדי שעותקים שנמצאים אצל התוקף יפסיקו לעבוד. היא לא אומרת במפורש אם המפתחות האלה הועתקו. היא כן מפרסמת סימן זיהוי ששווה לחפש בלוגים: חלק מהבקשות שהגיעו מהתוקף נשאו user agent מזויף של cPGuard/5.88.11.
אז למה זה חוזר דווקא בכלי האבטחה?
cPGuard הוא לא המקרה הראשון מהסוג הזה בחודשים האחרונים, והתבנית חוזרת מסיבה פשוטה: כלי אבטחה וכלי תשתית מותקנים עם ההרשאות הגבוהות ביותר שיש על המכונה, ומתעדכנים אוטומטית בלי שאף אחד מסתכל. זה בדיוק מה שהופך אותם לנתיב הפצה מושלם. הסיפור של צינור ההפצה של LiteLLM, שכיסינו בסוף אוקטובר, עבד על אותו היגיון: לא חולשה במוצר, אלא השתלטות על הדרך שבה המוצר מגיע ללקוח.
מה שהמקרה הזה מוסיף הוא כמה זול היה להתחיל אותו. בלי חולשת יום-אפס, בלי פישינג מול עובד, בלי שרשרת ניצול חולשות ארוכה. נרשמים כלקוח, מקבלים גרסת ניסיון, לומדים את המערכת מבפנים, ומוצאים את הדלת בעמוד callback של פורטל החיוב. שבע שעות וחצי עברו מהרגע שהתוקף נגע בפורטל לראשונה ועד שהייתה לו הרצת קוד על השרת שמחליט מה הלקוחות מתקינים.
מה זה אומר בפועל
מי שמריץ שרת אירוח עם cPGuard צריך קודם כול לבדוק אם הגיעה אליו אחת מהגרסאות הנגועות, דרך חיפוש הגרסאות 5.88.12 עד 5.88.19 ביומן ההתקנה של המוצר תחת /opt/cpguard/logs, ואז לעדכן ל-5.88.25 ומעלה. בהודעות מופיע שהעדכון הנקי 5.88.26 הוא זה שמסיר את ה-shell הנסתר, ושעדכון המשך מטפל גם במשימה המתוזמנת שנשתלה. העדכון הוא רק ההתחלה: OPSSHIELD מורה להחליף את סיסמת root ואת סיסמאות כל המשתמשים המקומיים ממערכת שחזור נקייה, להחליף את סיסמת ה-root של מסד הנתונים, לסובב כל סוד שעבר דרך שורת הפקודה של root, ולהנפיק מפתחות SSH חדשים ולהסיר את הישנים מכל שרת שסמך עליהם.
הנקודה האחרונה היא זו שנוטים לדלג עליה, והיא הכי חשובה כאן. אם מפתח ה-SSH של root נאסף משרת אחד, כל שרת אחר שמחזיק את המפתח הציבורי הזה ב-authorized_keys שלו חשוף גם הוא, גם אם cPGuard לא הותקן עליו מעולם. ולמי שיש חשבון בפורטל החיוב של OPSSHIELD, זה הזמן להחליף שם סיסמה ולהדליק אימות דו-שלבי.
תגובות