פנייה אחת לתמיכה, והסוכן קרא את ה-token מהזיכרון של התהליך שהריץ אותו: מה מצאה Unit 42 ב-AWS AgentCore
פנייה חדשה נכנסת למערכת התמיכה של חברה בדיונית בשם SupportCo. סוכן ה-AI שלה קורא אותה, שולף את פרטי הלקוח ומשיב. בתוך גוף הפנייה יושבת הערת HTML מוסתרת עם הוראה אחת קצרה: להוריד סקריפט ולהזרים אותו ל-python3. הסוכן קורא את הפנייה, מפעיל את כלי ה-shell שלו, והסקריפט רץ. רגע אחרי זה מגיע ל-webhook של התוקף token בגודל 1,034 בתים, ולצידו הכתובת שצריך כדי להשתמש בו.
את המחקר הזה פרסם ב-18 בספטמבר ניב רבין, חוקר ב-Unit 42 של Palo Alto Networks. ה-token הזה לא היה אמור להיות קריא לאף אחד: הוא ישב בכספת פרטי האימות של AWS AgentCore, מוצפן במנוחה ובתנועה, ובקוד של המפעיל הוא מופיע רק בתור מחרוזת הפניה. בהמשך נראה איך כלי אחד שמגיע דלוק כברירת מחדל הופך הזרקת טקסט לקריאה ישירה מהזיכרון של תהליך אחר, למה דווקא חשבון המפעיל הוא זה שנגנב, ומה ענתה AWS כשקיבלה את הדיווח.
מה זה harness, ולמה כלי shell משנה בו את המשוואה
מודל שפה לבדו יודע רק לייצר טקסט. כדי שיעשה משהו בעולם צריך סביבה שתריץ בשבילו פקודות, תחזיר לו תוצאות ותיתן לו זהות לפעול תחתיה. לשכבה הזאת קוראים harness, וזה בדיוק מה ש-AgentCore Harness של AWS מספקת: מצהירים מה הסוכן אמור לעשות, ו-AWS מנהלת את המחשוב, הזיכרון, הרשת והזהות.
על גבי מה שהמפעיל מצהיר, ה-harness מגיע עם שני כלים שדולקים מעצמם. לפי התיעוד של AWS, "כלי ברירת המחדל shell ו-file_operations זמינים בכל session אלא אם מגבילים אותם עם allowedTools", ובהמשך אותו עמוד כתוב במפורש שאם הפרמטר הזה לא הועבר, כל הכלים מותרים. shell מריץ פקודות bash, ו-file_operations פותח, יוצר ועורך קבצים.
כשסוכן קורא טקסט שהגיע מבחוץ, פנייה לתמיכה, מסמך, דף אינטרנט, אין לו דרך אמינה להבדיל בין הוראה שהמפעיל נתן לו לבין הוראה שמישהו שתל בתוך התוכן. לטכניקה הזאת קוראים prompt injection. כל עוד לסוכן יש רק כלים מוגדרים מראש, הנזק חסום פחות או יותר בגבולות מה שהמפתח בחר לחשוף. ברגע שיש shell, הגבול הוא כבר לא סכמת הכלי אלא רמת ההרשאות של סביבת ההרצה עצמה. "כל מה שכלי ה-shell יכול לגעת בו, מערכת הקבצים, הרשת, זיכרון התהליכים והשירותים שמאחוריו, הוא מה שהזרקה יכולה לגעת בו", כותב רבין.
הדרך לשם, אגב, לא הייתה חלקה. כשהחוקרים ביקשו מהמודל ישירות להריץ את פקודות הסריקה הוא סירב, וסירב שוב בניסיון הבא. הם החליפו את המודל דרך הגדרות ההפעלה למודל מתירני יותר שתומך בקריאת כלים, וגם אז התרגום מהבקשה לפקודה יצא מדויק רק חלקית, כי מנבא המילה הבאה לא מצטיין בשחזור תחביר shell תו בתו. מה שעבד בסוף היה הוראה אחת קצרה מספיק כדי שלא יתקלקל בדרך, מוזרקת דרך תוכן הפנייה.
root, ובלי אפילו ps
הדבר הראשון שהסקריפט עשה היה לשאול מי הוא. whoami ו-id החזירו root. לא חשבון שירות מוגבל, ולא משתמש בארגז חול. בסביבה הזאת לא היה מותקן ps, אז החוקרים קראו את רשימת התהליכים ישירות מ-procfs, מערכת הקבצים הווירטואלית שדרכה הקרנל חושף את מצב הריצה של כל תהליך.
מה שקפץ שם הוא PID 1: python3.10 -m loopy.server. הסקריפט שלהם, PID 40, היה צאצא ישיר שלו דרך ה-bash שהכלי הפעיל, PID 38, וכל השרשרת רצה תחת אותו UID. מכאן הבדיקה הבאה הייתה מתבקשת, והיא זו שפתחה את הכול: /proc/1/mem היה קריא.
לקרוא אותו בפועל דורש יותר מ-cat אחד, כי מפת הזיכרון של תהליך דלילה ומי שיעבור עליה ברצף ייתקל בפערים לא ממופים. אז הקריאה נעשתה בשני שלבים: קודם /proc/1/maps, שמחזיר את כתובות ההתחלה והסיום של כל אזור, את ההרשאות שלו ואת מה שממופה בו, ואחר כך מעבר אזור אחר אזור ב-/proc/1/mem וחיפוש תבניות בתוך מה שנקרא. --library-path בעץ התהליכים הצביע על /opt/amazon, ושם, לצד loopy/, ישבה גם חבילת bedrock_agentcore עם תת-המודול identity.
כספת מגנה במנוחה ובתנועה, לא בשימוש
AgentCore Identity היא הדרך שהפלטפורמה ממליצה עליה לאחסן פרטי אימות של סוכנים, והיא עושה את מה שמצופה מכספת: הצפנה במנוחה, הצפנה בתנועה, מפתחות KMS וגישה מגודרת ב-IAM. במקום לכתוב Bearer token בכותרת Authorization, כותבים בקונפיגורציה הפניה ל-ARN, כלומר למזהה המשאב בלבד.
הבעיה היא שבשלב מסוים ההפניה הזאת חייבת להפוך ל-token אמיתי, אחרת אי אפשר לשלוח אותה לשום מקום. הפתירה הזאת קורית בזמן ריצה, בתוך התהליך שמבצע את הקריאה. התהליך הזה הוא PID 1, ואת הזיכרון שלו החוקרים כבר ידעו לקרוא.
הסקריפט שהם כתבו לשלב הזה סרק את ה-heap אחרי שתי תבניות בלבד: token במבנה JWT, וכתובת שרת ה-MCP שאליו הוא נשלח. שניהם נמצאו, ושניהם יצאו בבקשת POST אחת אל שרת חיצוני. משם המשך הסיפור כבר לא רץ בתוך AWS בכלל: מהמחשב הנייד שלהם, בלי שום פרטי אימות של AWS, החוקרים התחברו לשרת ה-MCP, ביקשו את רשימת הכלים שלו, קראו ל-lookup_customer וקיבלו פרטי לקוחות, בהם שמות, מספרי טלפון וארבע הספרות האחרונות של מספר הזיהוי.
של מי ה-token הזה בכלל
כשפענחו את ה-token, השם שהופיע בו היה mcp-service. זה לא המשתמש ששלח את הפנייה, אלא חשבון השירות של המפעיל, וההבדל הזה הוא כל העניין.
טוקנים של משתמשי קצה בכלל לא מתאימים לכספת: הם נוצרים מחדש בכל התחברות, פגים מהר, ואי אפשר להזין אותם מראש לסוכן שמשרת אלפי אנשים. מה שכן יושב שם הוא פרטי האימות היציבים, אלה שמוגדרים פעם אחת בהקמת ה-harness ומשמשים אותו מול שירותים אחרים בכל session מחדש. וזה בדיוק מה שהפך אותם למטרה.
הגבול שנשבר כאן הוא גבול הרשאות ולא גבול הצפנה. המשתמש שפנה לתמיכה מורשה לדבר אחד, להפעיל את ה-harness. ה-harness עצמו מורשה להגיע לכל שירות שהמפעיל חיבר אליו, עם ההרשאות של המפעיל. רבין מנסח את זה בשורה אחת: מי שמורשה להפעיל אינו מורשה להגיע. הזרקה מוצלחת מוחקת את ההפרדה הזאת, כי מה שהסוכן יכול לעשות נקבע לפי מה שהוא מורשה, לא לפי מי ביקש.
מה ענתה AWS
לפי לוח הזמנים שמפרסמת Unit 42, הדיווח הועבר ל-AWS ב-19 במאי דרך HackerOne. ב-8 ביוני חזרו משם בבקשות שחזור והבהרות לגבי ההיקף, וב-10 ביוני הדיווח אוחד עם דיווח קודם שחולק איתו את אותו שורש בעיה, ונסגר כאינפורמטיבי במסגרת מודל האחריות המשותפת של AgentCore. ההנמקה, לפי אותו תיעוד, הייתה ש-allowedTools וסינון תעבורה יוצאת הן בקרות שנמצאות בצד הלקוח. לא הוקצה כאן מזהה CVE.
הנקודה שרבין מחזיר אליה היא שהבקרה שעליה AWS מצביעה תוחמת את בחירת הכלים רק בקריאת InvokeHarness, לא בהקמת ה-harness. גם התיעוד של AWS עצמו אומר את זה באותן מילים. מי שהגדיר harness פעם אחת ולא מעביר את הפרמטר בכל הפעלה ימשיך לקבל את shell בכל session, גם אם הוא בטוח שכיבה אותו מזמן.
מה זה אומר בפועל
מי שמריץ היום סוכנים על AgentCore צריך להעביר allowedTools בכל קריאת InvokeHarness ולתת רק את מה שה-session הספציפי באמת צריך, לצמצם כל חשבון שירות שיושב בכספת להרשאה המינימלית מול השירות שהוא מדבר איתו, ולנטר את התעבורה היוצאת מהמכולות של ה-harness. לגבי הניטור רבין מוסיף חידוד שקל לפספס: יעד יוצא שאינו ברשימת האינטגרציות שלכם הוא עדות להזרקה פעילה, לא סטייה בקונפיגורציה.
ולמי שבונה סביבות כאלה ולא רק צורך אותן, המסקנה רחבה יותר. השאלה ששווה לשאול על כל סביבת סוכנים מנוהלת היא לא אם פרטי האימות מוצפנים, אלא מי עוד רץ באותו מרחב זיכרון שבו הם נפתחים.
תגובות