تزریق __proto__ چیست
جمعبندی: JSON.parse با __proto__ بهعنوان کلید معمولی برخورد میکند؛ آسیب در ادغام بازگشتیای رخ میدهد که در Object.prototype مینویسد، پس هر شیء ویژگیهای مهاجم را به ارث میبرد.

جمعبندی: پیش از هر ادغام __proto__، constructor و prototype را مسدود کنید، ورودی نامعتمد را در Object.create(null) ادغام کنید و یک اعلان prototype به CI اضافه کنید؛ فقط وصله کردن کتابخانهها کافی نیست.
در یک جمله: کلید نامعتمد به نام __proto__ با یک ادغام بازگشتی برخورد میکند و ادغام در prototype مینویسد.
یک بازتولید
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)بیقید و شرط درست میشود - خرابی داده: فیلدهای غیرمنتظره در رکوردهای ذخیرهشده سریالایز میشوند
- منع سرویس: اشیای بزرگ متصل به prototype حافظه را بهطور دائم رشد میدهند
- تشدید: در ترکیب با موتورهای قالب یا اجراکنندههای فرمان، به اجرای کد از راه دور انجامیده است
چهار دفاع
کلیدها را فیلتر کنید.
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
مسدود کردن فقط __proto__ کافی نیست: constructor.prototype به همان جا میرسد.
در شیء بدون prototype ادغام کنید.
const safeTarget = Object.create(null);
بدون زنجیره prototype، __proto__ فقط یک کلید رشتهای است.
از Object.hasOwn بهجای in استفاده کنید. in زنجیره prototype را مشورت میکند.
وابستگیها را وصلهشده نگه دارید. lodash، deepmerge و json-patch همه برای این دسته باگ اصلاحیه منتشر کردهاند. npm audit را زیر نظر بگیرید.
تأیید کنید که اصلاح میماند
payloadی شامل __proto__ و constructor را از ادغامتان عبور دهید، سپس اعلان کنید:
console.log({}.polluted); // undefined
console.log(Object.prototype.polluted); // undefined
یک اعلان در CI، هزینه تقریباً صفر، رگرسیونهای بازسازیهای بعدی را میگیرد.
پرسشهای متداول
یک ادغام باید چه کلیدهایی را فیلتر کند؟
__proto__، constructor و prototype را با هم مسدود کنید. فیلتر کردن فقط __proto__ کافی نیست، چون constructor.prototype به همان prototype میرسد.
چرا Object.create(null) نوشتن را متوقف میکند؟
شیء بدون prototype زنجیره prototype ندارد، پس __proto__ کلید رشتهای معمولی میماند. بهعنوان ظرف داده کار میکند، اما جای فیلتر کلید یا بهروزرسانی وابستگی را نمیگیرد.
چگونه در CI برای آلودگی prototype آزمون بنویسم؟
payloadی شامل __proto__ و constructor را از ادغامتان عبور دهید، سپس اعلان کنید که {}.polluted و Object.prototype.polluted مقدار undefined دارند. یک اعلان تقریباً هیچ هزینهای ندارد و رگرسیونهای بازسازیهای بعدی را میگیرد.