בשרת ההרצה של התוקפים לא הייתה סיסמה. כל מה שהקבוצה בנתה, גנבה ותיעדה לאורך שבועיים ישב שם בספרייה פתוחה לרווחה: כלי הניצול, מסדי הנתונים של הסריקות, היסטוריית ה-bash של האופרטור, מאגרי הקוד שנלקחו מהקורבנות והנוזקות עצמן. חוקרי Acronis Threat Research Unit לא פרצו לשרת הזה. הם הגיעו אליו מהכיוון ההפוך, אחרי שציד איומים פנימי סימן implant לינוקס לא מוכר, והמעקב אחרי הדגימה נחת בדיוק שם.

הדוח, שפרסם החוקר Subhajeet Singha ב-13 בספטמבר, מתאר קבוצה דוברת סינית שהחוקרים עוקבים אחריה תחת השם Red Heron. היא לקחה קוד הדגמה ציבורי לחולשה קריטית ב-Gitea והפכה אותו בתוך ימים בודדים למערכת ניצול אוטומטית שסרקה 1,386 שרתים. נעבור על ארבעה דברים: איך החולשה עצמה עובדת, כמה מהר היא הפכה לקמפיין, מה נלקח מהקורבנות, ולמה שרת שהודבק בכלי האחרון בשרשרת הזאת כבר לא שווה ניקוי.

שתי פעמים אותו patch, ו-Git כותב את הקובץ לדיסק

Gitea היא פלטפורמה לניהול קוד מקור שארגונים מריצים על שרת משלהם, מעין GitHub פרטי שיושב ברשת הארגונית או, לא מעט פעמים, חשוף לאינטרנט. אחת היכולות שלה מאפשרת למשתמש לשלוח patch, קובץ טקסט שמתאר שינוי בקוד, והשרת מריץ עליו את git apply בתוך עותק זמני של המאגר.

כאן מתחילה הבעיה. הפקודה שהשרת מריץ אמורה להישאר ברמת ה-index של Git, כלומר לעדכן את רשימת השינויים הממתינים בלי לכתוב שום דבר לדיסק. אלא שהיא כוללת גם דגל שמפעיל מיזוג תלת-כיווני כמסלול גיבוי. מי ששולח את אותו patch פעמיים מייצר התנגשות בין שתי הוספות של אותו קובץ, מסלול המיזוג נכנס לפעולה ומוציא את הקובץ אל הדיסק, בניגוד לכוונה המקורית של הפעולה. העותק הזמני הוא bare, כלומר תיקיית המאגר עצמה משמשת גם כספריית העבודה הפנימית של Git, ולכן קובץ שנכתב לנתיב hooks/post-index-change נוחת ישירות בספריית ה-hooks הפעילה. hook הוא סקריפט ש-Git מריץ אוטומטית בכל פעם שמתרחשת פעולה מסוימת, ולכן בפעם הבאה ש-Git נוגע ב-index, הסקריפט של התוקף רץ בהרשאות של ה-service של Gitea.

זו CVE-2026-60004, בציון CVSS 9.8, והיא נוגעת בכל הגרסאות מ-1.17 ועד 1.27.0. NVD מתארת אותה באותו אופן, הרצת קוד מרחוק דרך ה-API של diffpatch באמצעות התקנת hook. את הדיווח המקורי רושמים החוקרים על שמו של Shai Rod (NightRang3r). Gitea תיקנה בגרסה 1.27.1 ב-27 ביולי, פשוט על ידי החלפת העותק הזמני מ-bare לעותק רגיל, מה שמוציא את תיקיית ה-hooks מהישג ידו של הקובץ הנכתב.

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

ארבעה ימים מקוד הדגמה לסריקה של 1,386 שרתים

הלוח זמנים ששחזרו החוקרים מהיסטוריית הפקודות של האופרטור הוא החלק שכדאי להסתכל עליו פעמיים. ב-27 ביולי יצא התיקון. למחרת פורסמה ההודעה המלאה, ואיתה קוד הדגמה עובד. ב-29 ביולי Red Heron כבר שכפלה את קוד ההדגמה מ-GitHub והתחילה לעבוד עליו.

ב-30 ביולי הסתיימה סריקה של 1,386 שרתי Gitea בשבע מדינות. היא הורצה דרך FOFA, מנוע חיפוש שמאנדקס מכשירים ושירותים חשופים לאינטרנט, בעזרת בדיקות אוטומטיות שרצו ב-50 תהליכים במקביל. מתוך התוצאות סוננו 128 מטרות שבהן ההרשמה הפתוחה נשארה דלוקה, והן רוכזו לרשימת תקיפה מתועדפת. למחרת נבנתה רשימה נפרדת של 477 שרתים בטייוואן.

בתחילת אוגוסט הכלי כבר היה בשל. הגרסה המאוחרת שלו מקבלת רשימת מטרות בפורמט JSON, פותחת חשבונות אוטומטית לפי תבנית שמות קבועה, מנצלת כל מטרה בתורה, מושכת את המאגרים ישירות ממערכת הקבצים ומנקה אחריה את העקבות ממסד הנתונים של Gitea. ב-3 באוגוסט הורצה מנה של חמישים מטרות טייוואניות, וחלק מהן נכשלו בהעברת המאגרים בגלל timeout. למחרת נוספו עוד חמישה קורבנות, ובאותו חלון התחבר האופרטור ב-SSH ל-NAS טייוואני שנפרץ והשיג עליו הרשאות root.

רשימת המטרות, כולה בסינית מפושטת

מה שנמצא במסד הנתונים של הסריקות הוא לא רשימת כתובות IP אלא תיק מודיעיני. כל מטרה סווגה לקטגוריה שנכתבה בסינית מפושטת: 国防/军工关联 (ביטחון ותעשייה ביטחונית), 选举/投票系统 (מערכות בחירות והצבעה), 能源企业 (חברות אנרגיה), 航天/空间 (תעופה וחלל), 电信企业 (תקשורת), 政府/市政/公共服务 (ממשל ושירותים ציבוריים). לכל רשומה הוצמד דירוג ביטחון (高, 中 או 低) ותגיות מקור כמו critical_infra.

לפי הספירה של Acronis, אחד עשר יעדים בחמש מדינות נפרצו בפועל. הבולט שבהם הוא חברת אנרגיה מתחדשת קנדית, שאליה חזר האופרטור ב-22 סשנים נפרדים. משם נלקחו מאגרי הקוד של מערכת משאבי האנוש, ה-CRM, שירות האימות והאינטרה-נט, יחד עם סודות תצורה שכללו token-ים ומפתחות SSH של השרת. בתיקייה ששייכת לקורבן הזה על שרת ההרצה ישבו 61 העתקים נפרדים של מסדי נתונים, שנמשכו דרך 41 חשבונות שנרשמו אוטומטית. על אותו שרת נשתלו שלוש רשומות מפתח בקובץ authorized_keys, ושתיים מהן נקראו במפורש backdoor ו-root-shell.

בטייוואן זה הלך רחוק יותר. משרת Gitea שרץ על NAS של Synology התקדם האופרטור אל אשכול Proxmox בן שלושה מארחים, השיג כרטיס אימות של root@pam והפעיל גיבוי מלא של מכונות וירטואליות על כל שלושת המארחים. גיבוי כזה הוא עותק של הדיסק השלם, על כל מה שיושב עליו.

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

JITTERLY, ומה שהוסתר בתוכו

ה-implant שהוביל את החוקרים לשרת מלכתחילה נקרא JITTERLY. זו תוכנה כתובה ב-C++ ללינוקס, שנשענת על פרוטוקול התקשורת של מסגרת ה-C2 הפתוחה Adaptix. שרת שליטה ובקרה (C2) הוא השרת שדרכו התוקף שולח פקודות לנוזקה ומקבל ממנה תשובות.

היא תומכת בלמעלה מ-30 פקודות: הרצת פקודות shell, העברת קבצים, מנהרות רשת, גישה לטרמינל אינטראקטיבי ופיבוט אל מכונות פנימיות. התקשורת רצה על TCP גולמי עם מסגור משלה, וכל הודעה עוברת שלושה שלבים לפני שהיא יוצאת אל הרשת: סריאליזציה ל-msgpack, הצפנה ב-AES-128-GCM עם מפתח סשן שהוחלף ברישום, ולבסוף הוספת כותרת אורך של ארבעה בתים.

כשהיא עולה בהרשאות root היא מעתיקה את עצמה לנתיב מערכתי תמים למראה תחת usr/lib, מוחקת את הקובץ המקורי ומריצה מחדש את העותק עם דגל שמורה לו לדלג על שלב המחיקה, כדי שלא תיווצר לולאה אינסופית של העתקה ומחיקה. החוקרים מציינים שלא הצליחו לקבוע איך היא חוזרת לפעול אחרי אתחול.

עד כאן הכלי שמדבר החוצה. מה שהיה מוצפן בתוכו מעניין יותר.

SIXZUT, ולמה אי אפשר פשוט לנקות שרת כזה

בתוך JITTERLY ישבה, מוצפנת, ספרייה שהחוקרים מתעדים בפעם הראשונה ומכנים SIXZUT. כדי להבין מה היא עושה צריך רגע על מנגנון בלינוקס: כמעט כל תוכנית נשענת על ספריות מערכת כדי לפתוח קבצים, לקרוא תיקיות או ליצור חיבורי רשת. קובץ בשם ld.so.preload אומר למערכת לטעון ספרייה מסוימת לפני כל השאר, בכל תוכנית שרצה על המכונה. מי ששולט בספרייה הזאת יושב בין כל תוכנית במערכת לבין מה שהיא מבקשת מהמערכת.

SIXZUT נשתלת בדיוק שם, מחופשת לספריית גרפיקה בשם libglthread.so.2, ומיירטת 15 פונקציות. התוצאה היא שרת שמשקר למי שמנהל אותו. בקשה להציג את תוכן התיקייה מחזירה רשימה שהקבצים של התוקף הוסרו ממנה. בדיקה של קובץ ה-preload עצמו מחזירה תשובה כאילו הוא לא קיים. חיבורי הרשת מוסתרים בשלושת המסלולים שבהם אפשר לראות אותם בלינוקס, כולל ממשק ה-netlink שדרכו כלים מודרניים מושכים את טבלת החיבורים הפתוחים.

שני הפרטים האחרונים הם אלה שקובעים מה עושים עם מכונה כזאת. הפונקציה שאחראית על שליחת סיגנלים לתהליכים מיורטת גם היא: היא קוראת קודם את שורת הפקודה של התהליך שמנסים להרוג, ואם הוא מופיע בטבלת ההסתרה היא מחזירה שגיאה שאומרת שאין תהליך כזה. אדמין שמריץ kill נגד התהליך לא מקבל שום הודעת שגיאה, והתהליך ממשיך לרוץ כרגיל. ומעבר לזה, אם ה-rootkit עולה בהרשאות root ומגלה שקובץ ה-implant נעלם או לא פועל, הוא מפצל תהליך חדש ומריץ אותו מחדש בעצמו.

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

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

מי שמריץ Gitea צריך להיות על 1.27.1 ומעלה, ולבדוק אם ההרשמה הפתוחה עדיין דלוקה. שרת שהיה חשוף לאינטרנט בין סוף יולי להיום דורש יותר מעדכון: כדאי לעבור על חשבונות ומאגרים שנוצרו מאז, לחפש בלוגים פניות ל-endpoint של diffpatch, ולהחליף כל מפתח SSH, token וסיסמה ששכבו על המכונה.

אם צצים סימנים ל-SIXZUT, קובץ preload שהשתנה או ספרייה שלא אמורה להיות שם, ההמלצה של Acronis היא לא לנקות אלא לבנות את השרת מחדש. כלי שמחזיר את עצמו לחיים ומשקר לכל פקודה שבודקת אותו לא משאיר לכם דרך אמינה לדעת שסיימתם.