TL; DR
חפץ של Maven Central שפורסם כ io.github.davidtimur:c2-lab שלח תשע גרסאות, שמונה מהן ביצעו מטען של גישה מרחוק במהלך קומפילציה של כל פרויקט במורד הזרם שהציב את ה-jar בנתיב מעבד ההערות שלו. לא היה צורך לייבא אותו בקוד יישום. לא היה צורך להפנות אליו שורת מקור. לא היה סקריפט התקנה, לא מקבילה לאחר ההתקנה, ולא וו מחזור חיים מכל סוג שהוא עבור הכלים לבדיקה.
וקטור הביצוע הוא קובץ יחיד של 46 בתים בתוך ה-jar: רישום ספק שירות של Java שמעניק שם למחלקה המיישמת javax.annotation.processing.Processorמהדר הג'אווה מגלה רישומים כאלה באופן אוטומטי. לאחר שנמצאו, javac יוצר מופעים ומפעיל את המחלקה כחלק רגיל מהקומפילציה. משמעות הדבר היא שסביבת זמן הריצה של המטען היא מכונת הבנייה, ברגע שמכונת הבנייה עושה את הדבר היחיד שלשמו היא קיימת.
לאורך תשע הגרסאות, ערוץ הפקודה והבקרה נבנה מחדש שלוש פעמים: כתובת URL חוזרת שסופקה על ידי המפעיל, לאחר מכן מעטפת הפוכה מעל מנהרת TCP של ngrok, ולאחר מכן ערוץ תשאול HTTP שנתיבים שלו השתנו פעמיים נוספות. הגרסה הסופית התקינה מנהל אמון TLS ללא פעולה כברירת מחדל של ה-JVM, קבלת כל תעודה למשך הקומפילציה.
החפץ נשא את המילים "C2 Lab Payload" ב-POM משלו וברישיון MIT. הוא היה פעיל ב-Maven Central במשך כל התקופה בה צפינו בו. מאז הוסר, יחד עם כל כולו io.github.davidtimur קבוצה.
אנטומיה: המהדר כמנוע ביצוע
מסגרת עיבוד ההערות של ג'אווה קיימת כדי שספריות יוכלו לייצר קוד בזמן קומפילציה - המנגנון שמאחורי Lombok, Dagger, ורשימה ארוכה של כלי ORM וסריאליזציה. מעבד מכריז על עצמו עם קובץ טקסט רגיל בתוך ה-jar:
מעבד META-INF/services/javax.annotation.processing.
תוכן הקובץ בכל מהדורה מושפעת, מילה במילה ומלאה:
io.github.davidtimur.c2lab.C2 Processor
הקובץ זהה מבחינת בייט בין גרסאות 1.0.1 עד 1.0.8, md5 7d2a08a5c8869a47eea9fa62487dfbe4. גרסה 1.0.0 אינה מכילה אותו.
מתי ג'אוואק כאשר הוא רץ, הוא סורק את נתיב מעבד ההערות עבור ערכי שירות אלה וטוען את מה שהוא מוצא. שום דבר בפרויקט המקומפל אינו צריך להזכיר את המעבד, להוסיף הערות או להגדיר דבר. נוכחות בנתיב מספיקה. זהו המאפיין שמפריד בין JavacDoor לדפוסי שרשרת האספקה שסביבם בנויים רוב הכלים:
| תבנית | הדק | גלוי כ |
|---|---|---|
| וו להתקנת npm | npm install | scripts.postinstall במניפסט |
| מטען בזמן ייבוא של פייתון | ייבוא ראשון של המודול | משפט ברמת המודול במקור |
| דלת ג'אווה | javac בכל פרויקט במורד הזרם | שם קובץ רישום שירות |
הוק התקנה (install hook) הוא הצהרה במניפסט, ומניפסט הוא הדבר הראשון שקוראים. מטעין בזמן ייבוא (import-time payload) לפחות נמצא בקוד מקור קריא. רישום שירות אינו אף אחד מהם: זהו שם קובץ ועוד שורה אחת שנותנת שם למחלקה, וההתנהגות נמצאת בקוד בייט (bytecode) קומפילציה במרחק ספרייה אחת.
מחלקת המטען עצמה מבצעת סיור מארח על ידי הפגזה. מאגרי הקבועים של המחלקות המקומפלות מכילים / Bin / sh, Whoami, uname -a, pwd בנתיב יוניקס ו tasklist בנתיב של Windows, יחד עם זרם שגיאות להפניה כדי למזג את פלט השגיאה של תהליך הילד לתוך הזרם שנלכד. שתי תבניות JSON נושאות תוצאות מהמארח - משואת רישום:
{"host":"%s","os":"%s","user":"%s","dir":"%s"}
מאוכלס מ- המארח הפקודה ואת שם מערכת הפעלה, user.name, ו user.dir מאפייני מערכת, וקריאה חוזרת לתוצאה:
{"version":"%s","host":"%s","time":"%s","output":"%s"}
סמני ההתקדמות שנותרו בפלט של המהדר עצמו גלויים באופן יוצא דופן: [C2] ביצוע בזמן קומפילציה הושלם, [C2] נשלחה קריאה חוזרת → HTTP , [C2] קליפה מחוברת ל .
תשע מהדורות, שלושה דורות של C2
הגרסאות אינן תשעה עותקים של מטען אחד. הן יומן איטרציות, וקריאתן לפי הסדר מראה שהערוץ נבנה מחדש בעוד ווקטור המסירה נשאר קבוע.
| לשחרר | ביצוע אוטומטי בעת קומפילציה | ערוץ |
|---|---|---|
1.0.0 | לא (אין קובץ שירות) | כתובת URL חזרה שסופקה על ידי המפעיל בלבד |
1.0.1 | כן (וקטור הוצג) | כתובת URL לקריאה חוזרת שסופקה על ידי המפעיל |
1.0.2 | כן | מעטפת הפוכה, מנהרת TCP של ngrok |
1.0.3 | כן | ערוץ HTTP, /register /cmd /out |
1.0.4 | כן | ערוץ HTTP, /register /cmd /out |
1.0.5 | כן | ערוץ HTTP, /register /poll /out |
1.0.6 | כן | ערוץ HTTP, /register /poll /out |
1.0.7 | כן | ערוץ HTTP, /register /cmd |
1.0.8 | כן | ערוץ HTTP + אימות TLS מושבת |
שלושה פרטים בטבלה זו ראויים לציון, משום שכל אחד מהם משנה את האופן שבו יש לקרוא את קבוצת החפצים.
הווקטור מגיע ל-1.0.1, לא 1.0.2. גרסה 1.0.0 נושאת את אותה לוגיקת סיור וקריאה חוזרת, אך אין לה קובץ שירות ואין ייבוא של עיבוד אנוטציות; היא פועלת רק אם משהו קורא לה. החל מגרסה 1.0.1 קובץ השירות קיים והמחלקות המקומפלות מייבאות. javax.annotation.processing.SupportedSourceVersionכל הערכה שמשווה בין שתי הגרסאות הנדרשות - 1.0.0 ו-1.0.2 - מסיקה נכון שמשהו השתנה, אך מזהה בטעות היכן.
המעטפת ההפוכה קיימת במהדורה אחת בדיוק. גרסה 1.0.2 מכילה 0.tcp.ngrok[.]io, שורת הלוגריתם [C2] מעטפת מחוברת ל-0.tcp.ngrok[.]io:19823, באנר אינטראקטיבי c2-shell, וסיום מסגרת __סוֹף__גרסאות 1.0.3 ואילך אינן מכילות אף אחת מאלה ובמקום זאת מגיעות למארח HTTPS. בקשת הסרה שתשם רק את הגרסאות עם כרטיס הייתה מצטטת נקודת קצה של TCP מתה תוך השארת ערוץ ה-HTTP החי, הקיים בשש גרסאות מאוחרות יותר, ללא אזכור.
הגרסה הסופית מסירה את אימות התעבורה. גרסה 1.0.8 מוסיפה מחלקה המיישמת javax.net.ssl.X509TrustManager ששיטות בדיקת התעודות שלו לא עושות דבר, מאמת שם מארח תמיד-אמת הרשום דרך מאמת שם מארח ברירת מחדלו אמון בכולם שגרה שמתקינה את שניהם כברירת מחדל של ה-JVM. התוצאה היא שבמשך שארית הקומפילציה, ה-JVM מקבל כל אישור מכל מארח - לא רק עבור התעבורה של המטען עצמו, אלא עבור כל דבר אחר שהבנייה עושה דרך TLS לאחר מכן.
ערוץ ה-HTTP, בכל שש הגרסאות המשתמשות בו, מופעל על ידי מארח יחיד: tableful-fervor-crazed.ngrok-free[.]devכל בקשה נושאת את הכותרת ngrok-skip-browser-warning, אשר מדכא את דף הביניים שמנהרות ngrok חינמיות מגישות לדפדפנים. קבוצות נתיבים משתנות בין גרסאות — /הַחוּצָה נעלמת ב-1.0.7, ו /cmd מתחלף עם /מִשׁאָל - אבל המארח אף פעם לא משתנה.
ההדרה העצמית מספרת
כל POM מגרסה 1.0.1 ואילך קובע ארגומנט מהדר עבור הבנייה של הארטיפקט עצמו:
-proc:none
דגל זה משבית עיבוד ביאורים. השפעתו כאן היא מוקדמתcisה: כאשר הפרויקט המכיל את המעבד עצמו עובר קומפילציה, המעבד אינו פועל.
לדגל הזה יש שימושים רגילים לחלוטין. פרויקט שמספק מעבד אנוטציות צריך לעתים קרובות להימנע מהחלת מעבד זה על עצמו במהלך האתחול, ותיעוד כלי הבנייה ממליץ בדיוק על כך. לבדו, זה לא מוכיח דבר.
יחד עם מה שהמעבד עושה, זה מתאר אסימטריה ספציפית: הקוד מבוצע על המכונות של כל מי שמבצע קומפילציה כנגד הארטיפקט, ולא על המכונה שבונה את הארטיפקט. הדגל מופיע באותה גרסה שמציגה את קובץ השירות - 1.0.1 - ובכל גרסה אחריה. המתאם בין "הגרסה שבה מתחילה ביצוע זמן קומפילציה" לבין "הגרסה שבה ביצוע זמן קומפילציה כבוי באופן מקומי" הוא האות האנליטי השימושי ביותר בקבוצת הארטיפקטים, והוא גלוי ב-POM בטקסט רגיל מבלי לבצע פירוק קומפילציה.
אנו מציינים את ההשפעה ועוצרים שם. שום דבר בחפצים לא מעיד על הסיבה לכך שהדגל הוצב.
פיסת מטא-נתונים נוספת ראויה לאזכור, בעיקר כדי להיפטר ממנה. תוכנית POM מכנה את הפרויקט "C2 Lab Payload", מתארת אותו כ"C2 lab payload artifact", ומעניקה לו רישיון ל-MIT. תיוג עצמי מסוג זה מוצע לעיתים כראיה לכך שחבילה היא פעילות מחקרית.cisולא איום חי, ולעתים קריאה זו נכונה - קנרי מוצהר ללא תשתית נגישה הוא אובייקט שונה מזה. זה לא חל כאן. שתל פונקציונלי שפורסם למאגר ציבורי, נגיש לכל צרכן, עם תשתית יוצאת שנבנתה מחדש שלוש פעמים בתשע מהדורות, הוא יכולת חיה ללא קשר לאופן שבו המטא-דאטה שלו קוראים לו. השם ב-POM לא משנה דבר לגבי מה שקורה במכונה שמבצעת קומפילציה נגדו.
אינדיקטורים למכונות בנייה
אם מארח בנייה ביצע קומפילציה כנגד ארטיפקט זה, הראיות נמצאות ביומני בנייה ובטלמטריה של הרשת ולא בשתל מתמשך בדיסק - המטען פועל בתוך תהליך המהדר ויוצא איתו.
במטמון ה-jar או במאגר המקומי
META-INF/services/javax.annotation.processing.Processorשמותio.github.davidtimur.c2lab.C2Processor- קובץ שירות md5
7d2a08a5c8869a47eea9fa62487dfbe4 - שיעורים תחת
io/github/davidtimur/c2lab/:C2Processor,C2Task,Task, וב-1.0.8 המחלקה הפנימיתTask$1
פלט בבנייה
[C2] compile-time execution complete[C2] callback sent → HTTP[C2] callback failed:[C2] shell connected to- באנר אינטראקטיבי
c2-shellסיום מסגרת__END__
טלמטריה בתהליך
javacכהורה של/bin/sh -c(יוניקס) או מפרש פקודות של Windows- פקודות ילדים
whoami,uname -a,pwd(יוניקס) אוtasklist(Windows) מוגן על ידי שלב קומפילציה
בטלמטריה של הרשת
- TCP יוצא אל
0.tcp.ngrok[.]io:19823(גרסה 1.0.2) - HTTPS אל
tableful-fervor-crazed.ngrok-free[.]dev, שבילים/register,/cmd,/poll,/out(מהדורות 1.0.3 עד 1.0.8) - כותרת הבקשה
ngrok-skip-browser-warning: true - בקשת התאמה של גופים
{"host":...,"os":...,"user":...,"dir":...}or{"version":...,"host":...,"time":...,"output":...}
בתצורה
- משתני סביבה
CALLBACK,CALLBACK_URLמאפיין מערכתcallback.url
מטא-נתונים של המוציא לאור
- קְבוּצָה
io.github.davidtimurכתובת המו"לdavudboi999@gmail[.]comמפתח חתימהE520C345EF94423D
גרסה 1.0.8 של גרסה 1.0.8 דורשת בדיקה נוספת. מכיוון שגרסה זו מתקינה מנהל אמון מתיר כברירת מחדל בכל רחבי JVM, כל חיבור TLS שנוצר מאוחר יותר באותו JVM - פתרון תלויות, העלאת ארטיפקטים, שלב פריסה - המשיך ללא אימות אישור. אין להניח שתעבורה מחלון זה מאומתת.
מדוע אובייקטים מורכבים זקוקים לסריקה שונה
JavacDoor הוא מקרה מבחן שימושי משום שהוא סותר שתי הנחות נפוצות בו זמנית, ואף אחד מהכשלים אינו ספציפי לכלים של ספק מסוים.
ההנחה הראשונה היא שקוד מסוכן מכריז על עצמו במניפסט. חלק ניכר מכלי שרשרת האספקה מאורגנים סביב מחזור החיים hooks, כי עבור npm ו-PyPI זה המקום שבו הפעולה בדרך כלל מתרחשת. ל-JavacDoor אין hook. הטריגר שלו הוא קובץ רישום שירות ששמו הוא ממשק Java ותוכנו הוא שם מחלקה. כדי לתפוס זאת באופן סטטי צריך לטפל מעבד META-INF/services/javax.annotation.processing. כנקודת כניסה לביצוע בפני עצמה, בדומה ל- לאחר ההתקנה script - ולאחר מכן לעקוב אחר המחלקה בעלת השם לתוך קוד הבייט. למערכות אקולוגיות יש מנגנוני גילוי אוטומטי משלהן בצורה זו, וכל אחד מהם הוא נקודת כניסה בין אם כלי עבודה מונה אותו כאחד ובין אם לא.
ההנחה השנייה היא שמחרוזות נמצאות בקבצי המקור. עבור JAR, נקודות הקצה, פקודות המעטפת, תבניות ה-JSON וסמני הלוג נמצאים כולם במאגרי הקבועים של .מעמד קבצים. כלי עבודה שמבצע grep text לא מוצא דבר - לא בגלל שהמחרוזות מעורפלות, אלא בגלל שהן נמצאות במכל בינארי מובנה שסריקת טקסט לא מנתחת. כל מחוון רשת בפוסט הזה הגיע מניתוח constant-pool. אחד מהם הוא מחוון מלא https:// כתובת URL נמצאת בתצוגה רגילה בתוך קובץ מחלקה; סריקת טקסט של התוכן הקריא של קובץ ה-jar עדיין לא תציג אותה. אין קידוד לעקוף כאן, רק פורמט מיכל לקריאה.
לשני הפערים יש את אותה צורה: פורמט ארטיפקט טופל כשקית של קבצים ולא כמבנה עם סמנטיקה מוגדרת. התיקון אינו זוהר - ניתוח המכולה, מינון נקודות הכניסה לגילוי אוטומטי של המערכת האקולוגית עצמה, ומעקב אחריהן לתוך קוד מהודר. עבור וקטורים בזמן בנייה ספציפית, בדיקה שלישית היא זולה ואבחנתית באופן מפתיע: השווה את מה שארטיפקט עושה לצרכנים לעומת מה שהוא פטור את עצמו ממנו. ארטיפקט שרושם מעבד בזמן קומפילציה ובו זמנית משבית עיבוד בזמן קומפילציה עבור הבנייה שלו עצמו, סיפר לך משהו על עצמו בשתי שורות של POM בטקסט רגיל.
עבור צוותים הצורכים כיום חפצי Maven, שלושה צעדים מעשיים כדלקמן:
- התייחס לנתיב של מעבד ההערות כאל גבול ביצוע. תלויות שנוחתות שם מריצות קוד בבנייה שלך. כאשר בנייה אינה זקוקה לעיבוד ביאורים, -proc:none שימושי מבחינה הגנתית כפי שהיה כנראה שימושי באופן מקומי כאן; היכן שכן, הוא מצמיד את מערך המעבדים במפורש במקום לרשת אותו מנתיב המחלקה של הקומפילציה.
- תת-תהליכים של מהדר יומנים. שלב קומפילציה שיוצר מעטפת הוא אנומלי ברוב הפרויקטים וניתן להתריע עליו באופן טריוויאלי.
- אל תתייחסו למטא-דאטה כאל עדות. "Lab", "test", "payload" ו-"PoC" בשם חבילה או בתיאור אינם מגבלות היקף. נגישות והתנהגות כן.
החפץ וכל קבוצתו הוסרו משם Maven Central בעקבות הדיווח שלנו; גם נתיב ה-artifact וגם נתיב ה-group מחזירים כעת 404, ואינדקס Central מדווח שאין קואורדינטות תואמות. פעולה זו סוגרת את ה-artifact הזה. היא לא סוגרת את הווקטור, שהוא תכונה מתועדת של מהדר Java וזמינה לכל מי שמפרסם קובץ jar.
הפניות
פוסט זה אינו מצטט מקורות חיצוניים. כל הממצאים הם ניתוח סטטי ממקור ראשון של תשעת הצנצנות שפורסמו, שנאספו מ-Maven Central לפני הסרתן. לא בוצע קוד מהארטיפקטים בשום שלב.







