
اقتراح TC39 للتخفيف من تلوث النموذج الأولي
المؤلفون: Santiago Díaz (Google)
المُتبنّي: Shu-yu Guo (Google)
المرحلة: 1
freeze وseal وpreventExtensions
هذا الاقتراح يسعى إلى التخفيف من ثغرة على مستوى اللغة تُعرف بتلوث النموذج الأولي (Prototype Pollution) عبر آلية تكمل بدائيات التجميد وآلية لجعل معظم قواعد الأكواد متوافقة معها. يصف ميزة اختيارية (opt-in) تجعل النماذج الأولية متاحة فقط من خلال واجهات برمجة الانعكاس (Reflection APIs). وبهذا، لم يعد بإمكان العبارة obj[key] الوصول إلى النماذج الأولية. قواعد الأكواد المتوافقة مع هذه الميزة تكون أكثر قصدية في طريقة استخدامها للنماذج الأولية.
ثغرات تلوث النموذج الأولي (PP) تسمح للمهاجمين بالتلاعب بكائنات لا يتحكمون بها أو لا يمكنهم الوصول إليها أثناء التشغيل. يمكن استخدام هذه البدائية "التأثير الشبحى عن بُعد" لتغيير شكل كائنات أخرى وتجاوز خصائصها، مما يؤدي إلى تلويث الكائنات في بيئة التشغيل.
الكائنات الملوثة تُبطل الافتراضات الأساسية للكود الذي كان سيكون آمنًا/صحيحًا، ويمكن أن تؤدي إلى تنفيذ أكواد عشوائية ومجموعة واسعة من مشكلات الأمان الأخرى في قواعد أكواد JavaScript. غالبًا ما تظهر أخطاء تلوث النموذج الأولي في تطبيقات الويب، ولكنها تؤثر أيضًا على بيئات تشغيل JavaScript غير المرتبطة بالويب.
خصائص الكائنات في JavaScript قابلة للكتابة بواسطة أي كود يمكنه الإشارة إليها. على وجه الخصوص، إذا اعتمدت العديد من الكائنات على خاصية مشتركة، يمكن لأي منها فرض تغييرات على جميع الكائنات الأخرى.
من الخصائص الخاصة لتلوث النموذج الأولي (PP) أنه هجوم يعتمد على البيانات فقط، إذ يسمح بتحقيق تنفيذ الأكواد عبر البيانات البحتة. على سبيل المثال، انظر إلى الكود الثغري التالي والاستغلال المقابل له:
// source is attacker-controlled
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
if(target[key] === undefined) {
target[key] = {};
}
target[key] = merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
// User input comes as a string
const userSuppliedObj = JSON.parse('{"__proto__": {"polluted": true}}');
// Trigger prototype pollution
merge({}, userSuppliedObj);
// Create a brand new object
const newObj = {};
// Has polluted property
console.log(newObj.polluted); // true
لاحظ أن الاستغلال قادر على تلويث إنشاء كائنات جديدة دون حقن أي كود خارجي.
بسبب هذه الخاصية الخاصة، فإن التخفيفات الحديثة ضد مشكلات تنفيذ الأكواد -مثل سياسة أمان المحتوى (CSP) أو الأنواع الموثوقة (Trusted Types)- تقصر عن الحماية من PP، لأنها تركّز على فرض مصدر الكود.
لاحظ أن هجمات البيانات فقط ذات صلة بالمواقف التي يكون فيها الكود المشغّل على الجهاز الافتراضي موثوقًا ويكون لتنفيذ الأكواد العشوائية تأثير أمني.
freeze وseal وpreventExtensionsتعاني بدائيات التجميد الحالية من مشكلات تصميم كبيرة تجعل من غير المرجح أن يتم اعتمادها على نطاق واسع. يمكن أن تكون مفيدة للمستخدمين الخبراء، لكنها غير مناسبة للاستخدام من قبل غالبية المطورين، الذين يتوقعون بشكل معقول أن تكون النماذج الأولية قابلة للتعديل:
تعاني واجهات برمجة التجميد من خطأ التجاوز وتناقضات أخرى تُدخل أخطاءً في قواعد الأكواد القائمة، مما يجعلها ترمي استثناءات أو، ما هو أسوأ، تفشل بصمت في الوضع المتراخي (sloppy mode). خلص تحقيق سابق في خطأ التجاوز إلى أن خطأ التجاوز يحدث في ~10% من قواعد الأكواد في الوضع الصارم و20% في الوضع المتراخي. أُسقط التحقيق بعد ذلك بوقت قصير.
تمنح واجهات برمجة التجميد المطورين مسؤولية ثقيلة تتمثل في معرفة النماذج الأولية التي يجب تجميدها للحفاظ على قاعدة أكواد آمنة، على افتراض أن المطورين خبراء أمنيون. تصف هذه الواجهات ماذا يجب أن يفعل المرء، وليس كيف يفعل ذلك. تجميد Object ليس جيدًا بالتأكيد، إذ تستغل العديد من الهجمات Array. ماذا عن Error، أو Date، أو Reflect، أو Proxy؟ أو الأنواع المدمجة المستقبلية؟ لا تقدم واجهات برمجة التجميد إجابات عن هذه الأسئلة.
تفترض واجهات برمجة التجميد وجود نقطة تجميد مستقرة: لحظة ثابتة أثناء التشغيل تكون فيها النماذج الأولية قد استقرت ويمكن تجميدها. عمليًا، هذه النقطة متقلبة وتتغير بمرور الوقت في قواعد الأكواد التي تُطوَّر بنشاط. وفي حين يمكن العثور على مثل هذه النقطة في العديد من التطبيقات اليوم، فإن إضافة تبعيات جديدة، وpolyfills، وتغييرات بنية الكود، والميزات القوية مثل الاستبدال الساخن (hotswapping) وأدوات المطورين تجعل نقاط التجميد هدفًا متحركًا.
لا يمكن لواجهات برمجة التجميد حماية سلسلة النماذج الأولية بالكامل. في JavaScript، يمكن إضافة الكائنات إلى سلسلة النماذج الأولية أو إزالتها منها في أي وقت. لحماية السلسلة بالكامل، يجب على المرء أن يتذكر دائمًا تجميد الكائنات المضافة إلى السلسلة، وهي عملية عرضة للخطأ. وعند إزالتها من السلسلة، لا يمكن إلغاء تجميدها بعد الآن.
باختصار: ميزة تُعرّض النماذج الأولية لواجهات برمجة الانعكاس فقط. إذا لم تكن النماذج الأولية متاحة عبر خصائص مثل __proto__ أو prototype، فلن تكون معرّضة لمشكلات البيانات فقط.
يُفهم هذا بشكل أفضل من خلال مثال: العبارة obj[one][two] = value معرّضة لتلوث النموذج الأولي عبر obj.__proto__.polluted. إذا حُذفت خاصية Object.prototype.__proto__، لم تعد العبارة نفسها معرّضة للثغرة لأنها لا تستطيع استخدام الطريقة الأخرى الوحيدة للوصول إلى النماذج الأولية، وهي obj.constructor.prototype.polluted. لاحظ أنه لا يمكن حذف النموذج الأولي.
يمكن تنفيذ هذا الاقتراح من خلال توفير واجهات برمجة الانعكاس وإنشاء ميزة تغليف (encapsulation) اختيارية جديدة تعمل على حذف خصائص النماذج الأولية. فيما يلي وصف لكل خطوة.
__proto__ هو اسم خاصية قديم يمكن حذفه، لكن الفتحة الداخلية (internal slot) خلفه لا تزال قابلة للقراءة عبر Object/Reflect.getPrototypeOf والكتابة عبر Object/Reflect.setPrototypeOf، مما سيجعل هذه الخاصية متاحة ببساطة للكود الذي يعمل بالفعل.
نقترح إنشاء واجهات برمجة جديدة لـ prototype، مثل getClassPrototypeOf وsetClassPrototypeOf، والتي من شأنها السماح بحذف اسم الخاصية دون تغيير طريقة عمل هذه الخاصية الخاصة ودعمها للجهاز الافتراضي بأي شكل من الأشكال.
يمكن تزويد واجهات برمجة الانعكاس بـ polyfill، مما يسمح لقواعد الأكواد المحصّنة بالعمل في جميع المتصفحات، بما في ذلك الإصدارات الأقدم.
ميزة "تغليف" اختيارية جديدة لا تُنشئ أي أسماء خصائص لدالتي الجلب (getter) والتعيين (setter) الخاصتين بفتحات النماذج الأولية، وهو أمر أصبح ممكنًا الآن لأن الإشارات إلى تلك الخصائص يمكن أن تستخدم واجهات برمجة الانعكاس بدلاً من ذلك.
يتم تفعيل الميزة عبر علامة خارج النطاق (out-of-band flag):
X-Encapsulate-Prototype: true--encapsulate-prototypeعندما يكون التغليف معطلاً، تكون النماذج الأولية متاحة عبر الخصائص وواجهات برمجة الانعكاس معًا.
عندما يكون التغليف مفعلاً، تكون النماذج الأولية متاحة فقط عبر واجهات برمجة الانعكاس، بعد حذف كل من __proto__ وprototype.
يتضمن التغليف أيضًا ميزة إعادة الهيكلة التلقائية التالية: