הסריקה מגיעה גם לסודות שנמחקו מהקוד מזמן: GhostAction חזר, ו-345 מאגרי GitHub נדבקו ביום אחד
ב-8 באוקטובר, בשעה 21:25:54 UTC, נכנס למאגר athenadriver של Uber קומיט עם הודעה שגרתית לגמרי: "Add security audit workflow". שדה המחבר נשא את שמו ואת כתובת המייל של Henry Wu, מי שכתב את הפרויקט במקור ושמר הרשאות כתיבה למאגר של הארגון. הקומיט לא היה חתום, הוא לא עבר דרך pull request, ואף אחד לא אישר אותו. שלוש שניות אחר כך GitHub התחיל להריץ את הקובץ שהוא הוסיף.
חוקרי StepSecurity פרסמו ב-9 באוקטובר את הניתוח הפורנזי של אותו יום: קמפיין גניבת פרטי האימות המכונה GhostAction השתלט על החשבונות של שני מתחזקים בולטים בקוד הפתוח וסחף 345 מאגרים בשני גלים אוטומטיים שנמשכו דקות. החידוש האמיתי הוא לא ההיקף. המטען שנדחף באוקטובר לא מסתפק בסודות שמוגדרים במאגר כרגע, הוא חופר בכל היסטוריית הקומיטים שלו. בהמשך נראה איך הקמפיין נכנס למאגר, מה בדיוק הוא מחפש שם, ולמה מאגר אחד יצא מכל זה בלי שריטה.
מי זה GhostAction, ולמה הוא לא נעלם מאז 2025
GitGuardian נתנו לקמפיין את שמו בספטמבר 2025, אחרי שספרו 817 מאגרים ציבוריים של 327 מפתחים ולפחות 3,325 secrets שיצאו מהם. StepSecurity, שכיסו את הגל ההוא במקביל, הגיעו לאותם מספרים. שרשרת התקיפה כמעט לא השתנתה מאז, וכדאי להבין אותה לפני המספרים.
בכל מאגר ב-GitHub אפשר להניח קובץ הגדרות בתיקייה .github/workflows, ו-GitHub ירוץ אותו אוטומטית בכל פעם שמתרחש אירוע מוגדר, למשל push חדש. לקובץ הזה יש גישה לערכים הסודיים שהמאגר שומר עבור תהליכי ה-build והפרסום שלו, כמו token של מרשם חבילות או מפתח של ספק ענן. המכניזם הזה נקרא GitHub Actions, וזה בדיוק מה שהתוקף מנצל כאן: הוא לא צריך חולשה באף רכיב, מספיק לו פרטי אימות של מתחזק אחד.
משם זה מכני. התוקף משיג token של מתחזק (לפי StepSecurity, ההסבר הסביר הוא personal access token שדלף מלוגים של נוזקות גניבת מידע), סורק את קבצי ה-workflow הקיימים במאגר כדי לדעת אילו שמות של secrets מוזכרים בהם, מכניס את השמות האלה לתוך המטען שלו, ודוחף אותו ישר לענף הראשי בזהות של הקורבן. ה-push הזה עצמו הוא שמפעיל את ההרצה הראשונה.
GitGuardian סופרים 772 מאגרים ציבוריים של 373 חשבונות שנדבקו בין 31 באוגוסט ל-30 בספטמבר 2026, עם 2,577 secrets שהמטענים כיוונו אליהם. ההזרקות הגיעו בפרצים: 143 מאגרים ב-31 באוגוסט, כ-400 בין 2 ל-5 בספטמבר (השיא, 294, היה ב-5 בספטמבר), ועוד 103 ב-15 בספטמבר. ב-92 מקרים התוקף בכלל לא הוסיף קובץ חדש. הוא לקח workflow זדוני שכבר שכב שם מגל קודם, ושינה לו רק את כתובת היעד.
מה המטען של אוקטובר מחפש
הגרסה הקודמת הסתפקה בשמות שהסריקה המקדימה מצאה. ה-workflow שנדחף באוקטובר, שנקרא "Security Audit", עושה משהו אחר: הוא מבקש מ-GitHub להוריד את המאגר על כל ההיסטוריה שלו, ואז מריץ על כל ההבדלים בין כל הקומיטים בכל הענפים חיפוש של שלוש עשרה תבניות של מפתחות. כלומר, סוד שנכנס לקוד פעם אחת, נמחק למחרת ונשאר בהיסטוריה, נקצר בדיוק כמו אחד שמוגדר היום. "סבב החלפה של ה-secrets המוגדרים ב-Actions לא מכסה יותר את החשיפה", כתבו החוקרים.
רשימת המטרות גדלה עם הזמן: מזהי מפתחות ומפתחות סודיים של AWS, token-ים זמניים של session, מפתחות API של Anthropic, OpenAI ו-OpenRouter, token-ים של GitHub מהסוג הקלאסי ומהסוג המדורג (fine-grained), token-ים של GitLab, מפתחות של Google ו-Firebase, token-ים של בוטים ב-Slack ומפתחות של SendGrid. מפתחות של ספקי AI הם כבר מטרה מלאה בפני עצמה, לא תוספת בשוליים.
ויש שם פרט אחד שמראה כמה המטען הזה תוכנן. מזהה מפתח של AWS לבדו לא שווה כלום בלי המפתח הסודי שמשתייך אליו, ושני הערכים האלה בדרך כלל יושבים בשורות סמוכות בקובץ הגדרות. לכן ה-workflow לא שולח רק את ההתאמות עצמן, אלא גם שתי שורות מכל צד של כל התאמה, עטופות בסימון ייחודי, כדי שהמפתח הסודי ייצא ביחד עם המזהה שלו.
והיציאה עצמה? בקשת HTTP רגילה, לא מוצפנת, לכתובת IP מספרית (193.32.204.199). אין פנייה ל-DNS, ולכן הגנה שמבוססת על חסימת דומיינים לא רואה כאן כלום. השרת גם מקבל סימון בתוך הכתובת לפי מה שנמצא במאגר, כך שהמפעיל ממיין את השלל לפי תשואה בלי לפתוח בכלל את גוף הבקשה.
היום שבו שני חשבונות סחפו 345 מאגרים
החלון הראשון נפתח ב-13:20 UTC מחשבונו של Takashi Kitao, מחבר מנוע המשחקים pyxel (18,400 כוכבים ב-GitHub), ובמהלכו נדחף ה-workflow ל-27 מאגרים. שמונה שעות אחר כך, בין 21:10 ל-21:26, נעשה שימוש בחשבונו של Henry Wu כדי לדחוף את אותו קובץ ל-318 מאגרים, מתוכם 39 מאגרי מקור ו-279 forks. athenadriver היה האחרון בתור.
מה-run log של אותה הרצה אפשר לראות מה נכנס לטווח: ה-checkout משך 13 ענפים ו-20 תגיות, כולל ענפי פיתוח אישיים וענף שנקרא s3-bucket-compromised-2023-03. ארבע שניות מתחילת ההרצה השרת של התוקף אישר קבלה. ב-pyxel ה-workflow רץ חמש פעמים, כולן בהצלחה, ושתיים מהן הופעלו ידנית ב-13:36:32 וב-13:42:35 מתוך הסשן של החשבון שנפרץ. זה לא רק סקריפט שרץ לבד. מישהו ישב שם ולחץ.
שני פרטים מאותו לוג מרככים מעט את התמונה. ה-token שההרצה קיבלה מ-GitHub הוגבל לקריאה בלבד, כך שה-workflow עצמו לא היה יכול לדחוף קוד או לפרסם חבילה; ההסלמה תלויה כולה בסודות שהוא גנב, וזאת בדיוק הסיבה שה-token-ים של PyPI ושל crates.io שנשמרו ב-pyxel חשובים כאן, שכן הספרייה מתפרסמת לשני המרשמים האלה. חוץ מזה, חלק מהתבניות שחיפשו מפתחות סודיים של AWS כתובות בתחביר שכלי החיפוש בלינוקס לא מקבל, ולכן הן מפספסות חלק מההתאמות. התבניות שמחפשות תחילית קבועה עובדות בלי בעיה.
ההגדרה היחידה שעצרה את השרשרת
ב-7 באוקטובר נדחפו למאגר plugins של typecho-fans, שנדבק עוד ב-5 בספטמבר, שני קומיטים בהפרש שתי שניות. הראשון היה קומיט ריק לגמרי, בלי שינוי בשום קובץ. השני שינה שלוש שורות ב-README. שניהם היו שם רק כדי להפעיל מחדש את ה-workflow הזדוני. שתי ההרצות נעצרו במצב של המתנה לאישור, כי המאגר הזה דורש אישור ידני להרצת workflows, והחילוץ לא קרה.
המספרים של GitGuardian מהגל של ספטמבר מספרים את אותו סיפור בגדול: מתוך 3,669 הרצות שהם אספו ב-605 מאגרים, רק 499 יצאו לדרך בפועל, ב-32 מאגרים. 336 מהן הסתיימו בהצלחה וחילצו 26 secrets מ-13 מאגרים. את כל השאר GitHub עצר והמתין לאישור אנושי שלא הגיע.
מי עוד מחזיק את אותם פרטי אימות
GitGuardian מצאו משהו שמסבך את ההנחה שמדובר בשחקן אחד. בהצלבה של קורבנות GhostAction עם פעילות זדונית אחרת ב-GitHub הם זיהו 13 מאגרים שנפגעו גם מכרייה של מטבעות קריפטו דרך GitHub Actions, פעילות שמשתייכת לארבעה קמפיינים נפרדים לפחות, עם ארנקים, בריכות כרייה ותשתיות שונות. ברוב המקרים הכורה הופיע חודשים אחרי ה-workflow של GhostAction, ובחלק הוא הגיע דווקא קודם, לפעמים בהפרש של ימים.
ההסבר הסביר בעיניהם הוא שה-token-ים של GitHub שבהם GhostAction משתמש אינם בלעדיים למפעיל שלו. token-ים שדלפו, לוגים של נוזקות גניבת מידע ומסדי פרטי אימות גנובים עוברים מיד ליד בין שחקנים שונים, וחשבון מפתח אחד שנפרץ יכול לשרת כמה תוקפים לא קשורים, כל אחד למטרה שלו. המסקנה המעשית חדה: החלפת ה-secrets שנגנבו לא סוגרת את הדלת, וצריך למצוא ולבטל את ה-token שאיפשר את ההזרקה מלכתחילה.
ובינתיים זה עוד פתוח. חיפוש קוד ב-GitHub אחרי כתובת השרת בקבצי workflow החזיר, לפי StepSecurity, 378 מאגרים עם המטען חי בענף הראשי ב-9 באוקטובר, והחיפוש הזה בכלל לא מגיע ל-forks שנדבקו. GitGuardian מצאו שרק 124 מאגרים, 16 אחוזים מהגל של ספטמבר, נוקו בפועל עד 5 באוקטובר. חבילה זדונית מתוך פרטי הפרסום שנגנבו עוד לא פורסמה בשום מרשם, ו-StepSecurity מדגישים שהיעדר שימוש לרעה עד כה אינו עדות לכך שהפרטים האלה בטוחים.
מה זה אומר בפועל
אם workflow בשם "Security Audit" או "Github Actions Security" הופיע באחד המאגרים שאתם מתחזקים מאז 31 באוגוסט, התייחסו לזה כאל פריצה מאומתת ולא כאל ניסיון. סבב ההחלפה צריך לכסות כל סוד שאי פעם נכנס לקומיט כלשהו במאגר, לא רק את מה שמוגדר ב-Actions עכשיו, ומיד אחריו צריך לאתר ולבטל את ה-token של המתחזק עצמו. את הקובץ הזדוני מוחקים מכל הענפים ולא רק מהראשי, כי הוא מופעל בכל push לכל ענף.
ושתי הגדרות שוות כאן יותר מכל סריקה בדיעבד: דרישת אישור להרצת workflows, שהיא מה שהציל את המאגר של typecho-fans, והגנת ענף על .github/workflows כך שכל שינוי בקבצים האלה יחייב pull request ועין נוספת.
תגובות