השמועות על השבתת חצי מהאינטרנט שוב הקדימו את המציאות. הבוקר התמלאו הפידים בדיווחים בהולים על כך שספריית הוולידציה הפופולרית Zod נפרצה, ושגרסה 4.6.6 כוללת נוזקה מסוכנת שגונבת מפתחות ומריצה קוד מרוחק. מפתחי TypeScript רבים כבר מיהרו לבדוק גיבויים, אבל אפשר להירגע: הקוד הזדוני נבלם ברמת ה-Git ולא הופץ מעולם כגרסה רשמית למאגר npm הציבורי. שום שרת פרודקשן לא נדבק מהתקנה רגילה, ואין שום סיבה לרוץ ולבצע שנמוך גרסאות בהול.
האירוע הזה אמיתי לגמרי ברמת הכוונה, אך כושל ברמת הביצוע. קומיט d821685 אכן נדחף ישירות למאגר colinhacks/zod תחת הכותרת התמימה Fix bugs and improve performance. התוקף ניצל הרשאות דחיפה כדי להעלות את מספרי הגרסאות בקובצי ה-JSON מ-4.6.5 ל-4.6.6. התקרית הזו הייתה חלק מגל רחב יותר שפגע בחבילות נוספות כמו nub ו-standard-schema, אבל ב-Zod שרשרת ההפצה החזיקה מעמד.
אזעקת שווא בשרשרת האספקה
מה שניסה התוקף לעשות היה מתוחכם על הנייר. בתוך השינויים שנדחפו למאגר GitHub של Zod, נוסף לקובצי ה-package.json הוק מסוג preinstall שהריץ קובץ חדש בשם meta.test.mjs. הרעיון ברור: ברגע שמפתח או שרת CI מריצים פקודת התקנה, הסקריפט מופעל אוטומטית עוד לפני שהספרייה עצמה נטענת לפרויקט.
מי שלא מושך קוד מענפי פיתוח לא נפגע.
נוזקה בחסות הבלוקצ'יין
החלק המעניין באמת בקוד הזדוני שנחשף ב-meta.test.mjs הוא השימוש בבלוקצ'יין כמנגנון פיקוד ושליטה. במקום לפנות לשרת C2 רגיל שקל לחסום ברמת ה-DNS, הסקריפט ביצע קריאות RPC לצמתים ציבוריים של Ethereum, בהם שירותים מוכרים כמו Cloudflare ו-llamarpc. הקריאה פנתה לחוזה חכם בכתובת ייעודית באמצעות הפונקציה eth_call כדי לשלוף נתונים מוצפנים.
הסקריפט פיענח את התשובה מהבלוקצ'יין כדי לקבל רשימת דומיינים, ניסה להוריד מהם מטען בבסיס 64, שמר אותו בתיקייה זמנית של מערכת ההפעלה, והריץ אותו תחת משתנה סביבה ייעודי. לאחר מכן דאג הקוד למחוק את התיקייה הזמנית כדי להעלים עקבות. התוקף בנה על תשתית מבוזרת שלא ניתן להפיל בקלות, בתקווה שהקוד יישאב למיליוני סביבות פיתוח ברחבי העולם.
a version bump on main publishes nothing by itself; a maintainer runs gh workflow run release.yml and approves the npm environmentהערת התיעוד המקורית שנמחקה בקובץ release.yml
ההבדל בין Git לרישום
המחסום שהכשיל את המתקפה היה תהליך האוטומציה. כפי שמופיע בבירור בהיסטוריית ה-diff ב-release.yml, תהליך השחרור של Zod לא מפרסם חבילות רק בגלל שדחפתם קוד ל-main. התוקף ניסה לשנות את קובץ ה-workflow כדי שדחיפה לענף הראשי תפעיל פרסום אוטומטי ל-npm, אבל השינוי הזה לא עזר לו לעקוף את מנגנון האישור הידני והסביבה המאובטחת.
שינוי קוד מקור במאגר Git אינו זהה להפצת חבילה חתומה ברישום החבילות. גרסה 4.6.6 של Zod כן פורסמה רשמית ב-npm וזמינה להורדה, אך היא שוחררה בצורה תקינה ונקייה מקוד זדוני לאחר שהשינויים בוטלו, בעוד שגרסאות שהתוקף ניסה להגדיר בהמשך כלל לא פורסמו. חברות הייטק וסטארטאפים בישראל שמשתמשים בספרייה מסביב לשעון לא נחשפו לסיכון ברשתות שלהם. הרעש ברשתות החברתיות נבע בעיקר מחוסר הבנה של ההפרדה בין ענף פיתוח לבין גרסה שמפורסמת למשתמשי הקצה. לא הייתי ממהר להתרגש מכל ציוץ לפני שבודקים את הרישום עצמו.
חבילה נקייה כבר שוחררה לכולם.
מה צריך לבדוק עכשיו
אם אתם מנהלים פרויקטים בסביבת Node.js או TypeScript, הבדיקה שלכם פשוטה ומהירה. פתחו את קובץ package.json וודאו שהתלות של Zod מוגדרת מול מספר גרסה רשמי ולא מושכת קוד ישירות מכתובת URL של GitHub או מענף main. כל עוד החבילות מגיעות ישירות מ-npm, המערכות שלכם בטוחות לחלוטין.
בנוסף, אם הפרויקט שלכם כולל חבילות אחרות שהושפעו באותו גל מתקפות כמו nub, שווה להריץ בדיקת תלויות שגרתית כדי לוודא שאינכם משתמשים בגרסאות נגועות שלהן. מעבר לכך, אפשר לסגור את ההתראות ולחזור לעבודה. לפעמים המנגנונים המשעממים ב-CI הם בדיוק מה שמציל אותנו מאסון.
מקורות
- Zod 4.6.6 compromised by malware?https://github.com/colinhacks/zod/commit/d821685ea6732436fcdf786a81d076958471e467
- release.ymlhttps://github.com/colinhacks/zod/commit/d821685ea6732436fcdf786a81d076958471e467#diff-87db21a973eed4fef5f32b267aa60fcee5cbdf03c67fafdc2a9b553bb0b15f34
- meta.test.mjshttps://github.com/colinhacks/zod/commit/d821685ea6732436fcdf786a81d076958471e467#diff-5acedd97e07708c715d41def1f6fea2b2dc7b126a06d71786130d28b1db4398d
- GitHubhttps://weba.co.il/tools/github/
- JSONhttps://weba.co.il/glossary/json/
הידיעה נכתבה מהמקורות שלמעלה בתאריך הפרסום, וכל טענה בה נבדקה מולם. מצאתם טעות? כתבו.