התיקון יצא בשקט, ואז מפתח חיצוני הסביר בפומבי איך מנצלים: שש רשתות בלוקצ'יין נפרצו בחמישה ימים
ב-20 באוגוסט 2026, ב-07:16 בבוקר לפי שעון UTC, נפתח pull request ציבורי במאגר של Push Chain, רשת בלוקצ'יין שבנויה על קוד של Cosmos. ה-pull request תיקן באג, וכדי להסביר למה הוא נחוץ הוא פירט מה הבאג עושה, מאיזה כיוון מגיעים אליו, ואילו גרסאות משוחררות עדיין חשופות. אחת עשרה שעות וחמישים דקות אחר כך רוקן תוקף את הרשת הראשונה.
Cosmos Labs פרסמה ב-28 באוגוסט דוח תחקיר על חמשת הימים שבאו אחרי. שש רשתות בלוקצ'יין נפרצו בין 20 ל-25 באוגוסט באותה שרשרת חולשות בדיוק, והתוקפים מכרו נכסים גנובים בשווי מוערך של כ-5.72 מיליון דולר. מה שהופך את התחקיר הזה למעניין הוא לא הסכום: התיקון כבר היה בחוץ שמונה שעות לפני אותו pull request, והוא שוחרר בשקט מוחלט ובכוונה תחילה. בהמשך נראה איך החולשה עובדת, למה חשבון של קורבן מתאפס דווקא כשמוסיפים לו טוקנים, ולמה Cosmos Labs בחרה לתקן בלי להגיד לאף אחד.
איפה בדיוק יושבת החולשה
Cosmos היא ערכת כלים שמאפשרת לכל אחד לבנות בלוקצ'יין משלו, ו-cosmos/evm הוא הרכיב שמאפשר לרשת כזאת להריץ חוזים חכמים בפורמט של את'ריום. הרכיב הזה יושב בין שני עולמות: מנוע ה-EVM מצד אחד, ומודול x/bank של Cosmos SDK, שמנהל את היתרות בפועל, מצד שני. התפקיד שלו הוא לוודא ששני הצדדים מסכימים כמה כסף יש לכל חשבון. שם בדיוק נמצאה החולשה.
בבלוקצ'יין של Cosmos אפשר להחזיק חשבון שהמטבעות בו משתחררים בהדרגה לאורך זמן, לפי לוח זמנים שנקבע מראש. חלק מהיתרה זמין להוצאה עכשיו, וחלק נעול עד שיגיע התאריך שלו. לחשבון כזה קוראים vesting account, והמנגנון מוכר לכל מי שקיבל אי פעם מניות או טוקנים כעובד.
כאן מתחיל הפער. מודול ה-staking של Cosmos, והממשק שדרכו חוזה חכם קורא לו, מרשים לחשבון כזה להפקיד לטובת ולידטור גם את החלק הנעול ולא רק את הזמין. אבל שכבת הרישום הפנימית של ה-EVM, ה-StateDB, מכירה רק את היתרה הזמינה. אז כשחשבון שיש בו עשרה מטבעות זמינים ומאה נעולים מפקיד מאה ועשרה, הקוד מנסה להחסיר מאה ועשרה מתוך עשרה.
בחשבון רגיל התוצאה שלילית. במספרים שלמים בלי סימן, כמו שה-EVM עובד, אין מספר שלילי: התוצאה מתגלגלת לקצה השני של הטווח, כלומר לכ-2 בחזקת 256. לתופעה קוראים integer underflow, וההודעה הרשמית מנסחת אותה יבש: פעולת SubBalance מחסירה את מלוא הסכום שהופקד מיתרה קטנה יותר. הבדיקה שהייתה אמורה לעצור את זה פשוט לא הייתה שם.
ואיך מרוקנים מזה חשבון של מישהו אחר
עכשיו לתוקף יש יתרה אסטרונומית, והיא חסרת ערך כשלעצמה. אף אחד לא יקנה ממנו 2 בחזקת 256 מטבעות, ואם ינסה למכור אותם הוא ישבור את הרשת שממנה הוא רוצה להוציא כסף. הצעד השני הוא מה שהופך את המספר הזה לסכום שאפשר לגשור החוצה.
התוקף שולח לחשבון אחר בדיוק 2 בחזקת 256 פחות היתרה של אותו חשבון. הרשת מאשרת את ההעברה, כי ברגע הזה המטבעות האלה קיימים ורשומים כתקינים. אצל הקורבן החיבור עובר את הקצה העליון וגם הוא מתגלגל, והיתרה מתאפסת. בסוף הפעולה החשבון של הקורבן ריק, והתוקף מחזיק בדיוק את מה שהיה בו. לפי הדוח נבחרו לתפקיד הזה חשבונות עם יתרות גבוהות שאין מאחוריהם בעלים פעיל, כמו כתובת האפס או ארנק רב-חתימות שנוצר עם הקמת הרשת.
שני השלבים רצו בטרנזקציה אחת, וזה החלק שדרש תכנון. כתובת של חוזה חכם ב-EVM נגזרת בצורה דטרמיניסטית, כלומר אפשר לחשב אותה מראש. התוקף חישב את הכתובת שבה ייווצר החוזה שלו, פתח בה קודם חשבון vesting, ורק אז פרס שם את החוזה. כך אותו חוזה יכול היה גם להפקיד את היתרה הנעולה שלו וגם להעביר את התוצאה לקורבן, בפעולה אחת שהשינוי הנקי שלה בהיצע המטבעות הוא אפס. רשת שמנטרת קפיצות בהיצע לא רואה כלום.
ההתנהגות נבדלה בין שורות הגרסאות. שורת 0.6.x מנפיקה ושורפת מטבעות בספר החשבונות של ה-SDK, ולכן ההנפקה הענקית גורמת לגלישה בהיצע הכולל ומשביתה את הרשת. שורת 0.7.x כותבת יתרות ישירות ל-x/bank ומקבלת כל ערך ששורד המרה מ-uint256 ל-int256, כלומר בולעת את השינוי בשקט. ההודעה הרשמית מסמנת כחשופות את הגרסאות שמתחת ל-0.6.2 ואת 0.7.0 עד 0.7.2.
אז למה התיקון פורסם בשקט?
ל-Cosmos Labs יש מדיניות מפורסמת שמבדילה בין שני מסלולים. חולשה שמסכנת כספים ברשתות חיות מקבלת הפצה פרטית של התיקון דרך ערוצים מאובטחים, לפני כל פרסום. חולשה שלא מסכנת כספים מתוקנת במסלול השקט: התיקון נכנס לקוד הפתוח כמו כל commit אחר, בלי הכרזה, מתוך הנחה שאף אחד לא יזהה מה הוא עושה.
הדיווח המקורי הגיע ב-25 באפריל דרך תוכנית ה-bug bounty, והוא כלל הוכחת היתכנות לרשת עם שש ספרות אחרי הנקודה העשרונית. הצוות ניסה לשחזר על תצורות נוספות, לא הצליח ברשתות עם 18 ספרות, והסיק שהבעיה נוגעת רק לתצורות הנדירות. כל הרשתות הידועות בייצור עובדות ב-18 ספרות, ולכן ההערכה הייתה שאין סיכון לכספי משתמשים. התיקון נכנס ל-branch הראשי ב-15 במאי ולא הועבר משם לענפי הגרסאות, כי הוא משנה מצב ומחייב שדרוג מתואם של כל הרשת.
בתחילת אוגוסט הגיעו דיווחים נוספים מחוקרים עצמאיים, ואיתם המידע שאיפשר לשחזר את החולשה גם ב-18 ספרות. ב-13 באוגוסט כבר היה ברור שכל רשתות cosmos/evm חשופות. "בשלב הזה, כשמדובר בחולשה שידוע שהיא מסכנת כספי משתמשים ברשתות ייצור, הצוות היה נוהג בדרך כלל להשתמש בערוצים מאובטחים כדי להפיץ תיקון באופן פרטי לרשתות המושפעות", נכתב בדוח. הם לא עשו את זה. ההיגיון שהוצג בדיעבד: מכיוון שהתיקון כבר ישב שלושה חודשים ב-branch הראשי בלי שאיש ניצל אותו, נראה היה בטוח להישאר במסלול השקט. ב-19 באוגוסט הקוד עורפל, הועבר לענפי v0.6.x ו-v0.7.x, וב-23:01 יצאו v0.6.2 ו-v0.7.2 עם הערות שחרור שמזכירות אבטחה ולא מתארות דחיפות או חומרה.
שמונה שעות אחר כך
וכאן נכנס לתמונה הpull request של Push Chain. הוא מתאר את החולשה ואת דרך הניצול שלה בפירוט, מייחס את הממצא לביקורת של חברת Hacken, וכולל טבלה של הגרסאות המשוחררות שעדיין חשופות. הוא לא מזכיר את v0.6.2 ואת v0.7.2, שיצאו שמונה שעות קודם לכן, וקובע ששדרוג לגרסה משוחררת לא פותר את הבעיה. מי שקרא אותו יצא עם התמונה ההפוכה מהמציאות: תיאור מלא של הבאג, ומסקנה שאין לו תיקון.
ב-19:06 באותו ערב הותקפה MANTRA. מתקפה שנייה הגיעה ב-22:59, והרשת עצרה את עצמה ב-23:13, ארבע עשרה דקות אחריה. ההודעה המאובטחת הראשונה של Cosmos Labs לרשימת אנשי הקשר יצאה ב-03:36 למחרת, כשעתיים אחרי ש-MANTRA דיווחה. ב-22 באוגוסט ב-19:46 הגיע התור של TAC: כמעט שלושה מיליארד טוקני TAC נמשכו מקרן ההפקדות של הרשת, גושרו ל-BNB Chain, ושם נמכרו כ-1.2 מיליארד מהם תמורת 950,293 דולר ב-USDT לאורך כ-80 טרנזקציות קטנות, בקצב שנועד לא להזיז את המחיר. באותו ערב נפרצה גם KiiChain, ב-18 חזרות של אותה שרשרת. ב-23:45 שלחה Cosmos Labs הודעה שקוראת לכל הרשתות לעצור את עצמן מיד במקום לבצע שדרוג מתואם. שדרוג מתואם לוקח ימים, ולתוקף היו שעות.
חלק מהמספרים ראוי לסייג. סכומי הגניבה הגיעו מהרשתות שנפגעו עצמן, ובדוח נכתב במפורש שהם לא עברו ביקורת בלתי תלויה. (ולמען ההגינות, לפי אותו דוח 37 תיקונים שקטים עברו ב-13 החודשים האחרונים בלי שאף מפתח במורד הזרם פירסם בפומבי איך מנצלים אותם.)
מה מודים שצריך לשנות
הכשל השני כאן יושב בכלל מחוץ לקוד. המאגר פתוח וכל אחד יכול לפרוס אותו, ולכן אין ל-Cosmos Labs רישום מלא של מי מריץ אותו: בדוח נכתב שהמערכת האקולוגית מונה יותר מ-115 רשתות ציבוריות ידועות, ושבמהלך הטיפול באירוע התגלו אחת עשרה רשתות נוספות שמריצות את cosmos/evm ומעולם לא נרשמו לערוצי ההתראה של החברה. כשצריך להזהיר את כולם תוך שעות, רשימת תפוצה חלקית היא חולשה בפני עצמה. בסך הכול תואמה העבודה מול 40 רשתות בחמישה ימים.
החלק המעניין בדוח הוא לא הטכניקה אלא ההודאה בכשל תהליכי. Cosmos Labs מתחייבת לבנות דרך טובה יותר לזהות מקרים שבהם החולשה רחבה ממה שהדיווח המקורי תיאר, וזה בדיוק מה שקרה כאן: הערכת סיכון שנשענה על ניסיון שחזור שנכשל. במקביל היא מתחייבת להגדיר סטנדרט פומבי לשתי שאלות שלא היו מוגדרות: מתי ממליצים לרשת לעצור לגמרי במקום לשדרג, ומתי משתמשים בערוץ פרטי במקום במסלול השקט. ולסיום היא מבטיחה ביקורת חיצונית על נהלי האבטחה התפעוליים שלה.
התיקון השקט הוא הימור, וכדאי לנסח אותו בגלוי: הוא מניח שהתוקפים לא יקראו את הקוד הפתוח בעיון שבו יקראו אותו המגינים. ההימור הזה עובד היטב ברוב המקרים, והוא נשבר לא מול תוקף מתוחכם אלא מול מפתח תמים שרצה לתקן את הפורק שלו ולתעד למה.
מה זה אומר בפועל
מי שמפעיל רשת שמריצה את cosmos/evm צריך להיות על v0.6.2 או v0.7.2, ולוודא שאיש קשר מטעמו נמצא ברשימת ההתראות המאובטחת של Cosmos Labs. אחת עשרה רשתות גילו כאן שהן לא היו שם. הלקח הרחב יותר נוגע לכל מי שמתחזק פורק של פרויקט קוד פתוח: ברגע שאתם מתארים בפומבי מה תיקון אבטחה מתקן, אתם כותבים מפרט לכל מי שקורא, לא רק למי שאמור לשדרג. אם מצאתם באג כזה אצלכם, הכתובת היא ערוץ הדיווח המתואם של הפרויקט המקורי, ורק אחר כך תיאור ציבורי.
תגובות