לדלג לתוכן

מה זה Prototype Pollution ולמה קל כל כך לפספס אותה בסקירת קוד

אם עברתם על SQL Injection ו-XSS ואתם מרגישים שאתם כבר "מבינים איך אתרים נשברים", יש לכם עוד חולשה אחת שכדאי להכיר, וכזו שדווקא מבלבלת הרבה אנשים כי היא לא נראית כמו חולשה בכלל. Prototype Pollution לא מזריקה קוד לדף, לא שולחת שאילתה ל-SQL, ולפעמים אפילו לא נראית כמו קלט זדוני. היא פשוט מנצלת איך JavaScript עובד מתחת למכסה המנוע, וזה בדיוק מה שהופך אותה למסוכנת.

אז מה זה בעצם Prototype Pollution

כדי להבין את החולשה, צריך להבין רגע אחד בסיסי ב-JavaScript - ירושה מבוססת פרוטוטייפ. כל אובייקט בשפה יורש תכונות מ"אב קדמון" משותף שנקרא Object.prototype. אם אני יוצר אובייקט ריק לחלוטין, {}, הוא עדיין "יודע" איך לעשות toString() או hasOwnProperty(), למרות שאף אחד לא הגדיר לו את זה במפורש. הוא פשוט שואל את הפרוטוטייפ.

עכשיו הבעיה: אם אני מצליח להוסיף תכונה חדשה ל-Object.prototype עצמו, כל אובייקט בתוכנית, בלי יוצא מן הכלל, פתאום "יורש" אותה. לא רק האובייקט שיצרתי, אלא כל אובייקט שקיים או ייווצר במערכת מאותה נקודה והלאה. זה כמו לשנות את ה-DNA המשותף של כל האובייקטים באפליקציה בבת אחת.

איך תוקף בכלל מגיע ל-Object.prototype

זה קורה כמעט תמיד דרך פונקציות שמבצעות מיזוג עמוק - deep merge - בין שני אובייקטים, או פענוח JSON שמוזרם ישירות לתוך מבנה נתונים קיים. פונקציות כאלה עוברות רקורסיבית על כל המפתחות של אובייקט הקלט ומעתיקות אותם ליעד. הבעיה מתחילה כשהקלט מכיל מפתחות מיוחדים כמו __proto__, constructor או prototype.

המפתח __proto__ הוא בעצם דרך גישה ישירה לפרוטוטייפ של האובייקט. אם פונקציית המיזוג לא בודקת את שם המפתח לפני שהיא כותבת אליו, ותוקף שולח {"__proto__": {"isAdmin": true}}, הפונקציה בתמימות "ממזגת" את זה - ולמעשה כותבת ישירות ל-Object.prototype. המפתח constructor.prototype הוא נתיב עקיף לאותו מקום, וקוד הגנה שבודק רק אם המפתח שווה בדיוק ל-__proto__ מפספס אותו לגמרי. זה קורה בדרך כלל בפונקציות עזר "תמימות" - מיזוג הגדרות משתמש, בניית אובייקט מתוך query string, פענוח body של בקשת HTTP - שאף אחד לא חושב עליהן כעל משטח תקיפה.

למה זה כל כך קשה לתפוס בסקירת קוד

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

מה אפשר לעשות עם זיהום כזה בפועל

ברגע שתוקף מצליח לזהם את הפרוטוטייפ, ההשלכות תלויות בקוד שסביב:

  • הזרקת תכונות ללוגיקה עסקית. אם קוד באפליקציה בודק if (user.isAdmin), וכל אובייקט מעכשיו יורש isAdmin: true מהפרוטוטייפ המזוהם, פתאום כל משתמש הוא מנהל.
  • מניעת שירות - Denial of Service. זיהום שגורם לפונקציות מובנות להתנהג אחרת, או שיוצר לולאה אינסופית בקוד שסומך על מבנה מסוים, יכול להפיל את השרת כולו.
  • הרצת קוד מרחוק בצד שרת. זה החלק הכי חמור, והוא רלוונטי בעיקר ב-Node.js. שילוב של Prototype Pollution עם קוד שמשתמש בתכונות מסוימות של אובייקטים בצורה לא זהירה, למשל בספריות תבניות, יכול להוביל להרצת קוד שרירותי על השרת. זה לא קורה בכל מקרה, אבל כשזה קורה, זו כבר לא חולשה תיאורטית.

איך מזהים ואיך מונעים

מבחינת הגנה, הרעיון הבסיסי הוא לא לסמוך על שמות מפתחות שמגיעים מקלט חיצוני. פונקציות מיזוג צריכות לבדוק ולחסום מפתחות כמו __proto__, constructor ו-prototype באופן מפורש, ועדיף להשתמש בספריות מיזוג מעודכנות שכבר טיפלו בבעיה הזאת, במקום לכתוב פונקציית מיזוג רקורסיבית משלכם. אפשר גם ליצור אובייקטים עם Object.create(null), שאין להם פרוטוטייפ בכלל, כשרלוונטי.

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

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

הצטרפו לקהילה בדיסקורד

לסיכום

Prototype Pollution היא תזכורת טובה לכך שלא כל חולשה נראית כמו קלט זדוני קלאסי. לפעמים החולשה יושבת בהבנה הבסיסית ביותר של השפה שבה אתם עובדים כל יום. ברגע שמבינים איך ירושת פרוטוטייפ עובדת ב-JavaScript, קל הרבה יותר לזהות אותה, גם כתוקפים וגם כמפתחים שרוצים למנוע אותה מראש.