JSON Toolkit

מהי הזרקת __proto__

שורה תחתונה: JSON.parse מתייחס ל-__proto__ כמפתח רגיל; הנזק קורה ב-merge רקורסיבי שכותב ל-Object.prototype, כך שכל אובייקט יורש מאפיינים של התוקף.

מתקדםכ-5 דקות קריאהפורסם 2026-10-30עודכן 2026-10-30
איך prototype pollution עובד ואיך לחסום אותו

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

במשפט אחד: מפתח לא מהימן בשם __proto__ פוגש merge רקורסיבי, וה-merge כותב לפרוטוטייפ.

שחזור

function merge(target, source) {
  for (const key in source) {
    if (typeof source[key] === 'object' && source[key] !== null) {
      target[key] = target[key] || {};
      merge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

merge({}, JSON.parse('{"__proto__": {"isAdmin": true}}'));
console.log({}.isAdmin);   // true

שום אובייקט עסקי לא נגע. Object.prototype כן, וכל אובייקט עכשיו נושא isAdmin.

למה JSON.parse לא אשם

const data = JSON.parse('{"__proto__": {"a": 1}}');
Object.getOwnPropertyNames(data);                  // [ '__proto__' ]
Object.getPrototypeOf(data) === Object.prototype;  // true, untouched

__proto__ מגיע כמאפיין עצמי רגיל. הסכנה כולה בקוד שממזג, מעתיק לעומק או מגדיר לפי נתיב — _.merge, כמה דפוסי Object.assign, ואינספור עוזרי set(obj, path, value) שנכתבו ביד.

השפעה

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

ארבע הגנות

סננו את המפתחות.

const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);

חסימה של __proto__ לבדו לא מספיקה: constructor.prototype מגיע לאותו מקום.

מזגו לאובייקט עם פרוטוטייפ null.

const safeTarget = Object.create(null);

בלי שרשרת פרוטוטייפ, __proto__ הוא רק מפתח מחרוזת.

השתמשו ב-Object.hasOwn במקום in. in מתייעץ עם שרשרת הפרוטוטייפ.

שמרו על תלויות מעודכנות. lodash, deepmerge ו-json-patch הוציאו תיקונים לכל סוג הבאג הזה. עקבו אחרי npm audit.

ודאו שזה נשאר מתוקן

העבירו payload שמכיל __proto__ ו-constructor דרך ה-merge שלכם, ואז בדקו:

console.log({}.polluted);                 // undefined
console.log(Object.prototype.polluted);   // undefined

Assertion אחד ב-CI, עלות כמעט אפסית, תופס רגרסיות מרפקטורים מאוחרים יותר.

שאלות ותשובות

אילו מפתחות merge צריך לסנן?

חסמו __proto__, constructor ו-prototype יחד. סינון של __proto__ לבדו לא מספיק, כי constructor.prototype מגיע לאותו פרוטוטייפ.

למה Object.create(null) עוצר את הכתיבה?

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

איך בודקים prototype pollution ב-CI?

שלחו payload שמכיל __proto__ ו-constructor דרך ה-merge שלכם, ואז בדקו ש-{}.polluted ו-Object.prototype.polluted הם undefined. assertion אחד עולה כמעט כלום ותופס רגרסיות מרפקטורים מאוחרים יותר.

קריאה נוספת