JSON Toolkit

ما هو حقن __proto__

الخلاصة: JSON.parse يعامل __proto__ كمفتاح عادي؛ والضرر يحدث في دمج تكراري يكتب إلى Object.prototype، فيرث كل كائن خصائص المهاجم.

متقدمقراءة نحو 5 دقيقةنُشر في 2026-10-30حُدِّث في 2026-10-30
كيف يعمل تلوث النموذج الأولي وكيف تحجبه

الخلاصة: احجب __proto__ وconstructor وprototype قبل أي دمج، وادمج المدخلات غير الموثوقة في Object.create(null)، وأضف تأكيدًا للنموذج الأولي في CI؛ إصلاح المكتبات وحده لا يكفي.

بجملة واحدة: مفتاح غير موثوق اسمه __proto__ يقابل دمجًا تكراريًا، فيكتب الدمج إلى النموذج الأولي.

إعادة إنتاج

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 يصل إلى المكان نفسه.

ادمج في كائن بلا نموذج أولي.

const safeTarget = Object.create(null);

بلا سلسلة نموذج أولي، يبقى __proto__ مجرد مفتاح سلسلة.

استخدم Object.hasOwn بدلًا من in. ‏in يستشير سلسلة النموذج الأولي.

أبقِ التبعيات مصلحة. lodash وdeepmerge وjson-patch أصدرت جميعها إصلاحات لهذه الفئة من الثغرات. راقب npm audit.

تحقق من بقائه مصلحًا

مرّر حمولة تحتوي على __proto__ وconstructor عبر دمجك، ثم أكّد:

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

تأكيد واحد في CI، بكلفة تقارب الصفر، يلتقط الانحدارات من إعادات الهيكلة اللاحقة.

الأسئلة الشائعة

ما المفاتيح التي يجب أن يرشحها الدمج؟

احجب __proto__ وconstructor وprototype معًا. ترشيح __proto__ وحده غير كافٍ، لأن constructor.prototype يصل إلى النموذج الأولي نفسه.

لماذا يوقف Object.create(null) الكتابة؟

كائن بلا نموذج أولي لا يملك سلسلة نموذج أولي، فيبقى __proto__ مفتاح سلسلة عاديًا. يعمل حاوية بيانات، لكنه لا يغني عن ترشيح المفاتيح أو تحديث التبعيات.

كيف أختبر تلوث النموذج الأولي في CI؟

أرسل حمولة تحتوي على __proto__ وconstructor عبر دمجك، ثم أكّد أن {}.polluted وObject.prototype.polluted قيمتهما undefined. التأكيد الواحد لا يكلف شيئًا تقريبًا ويلتقط الانحدارات من إعادات الهيكلة اللاحقة.

قراءة إضافية