حول آلية React لمنع إمكانية حقن JSON for XSS ، وتجنب نقاط الضعف الشائعة.
قد تعتقد أنك تكتب JSX:
<marquee bgcolor="#ffa7c4">hi</marquee>
ولكن في الواقع كنت تتصل الوظيفة:
React.createElement( /* type */ 'marquee', /* props */ { bgcolor: '#ffa7c4' }, /* children */ 'hi' )
وهذه الدالة تقوم بإرجاع كائن عادي يسمى عنصر React. وفقًا لذلك ، بعد اجتياز جميع المكونات ، يتم الحصول على شجرة كائنات مشابهة:
{ type: 'marquee', props: { bgcolor: '#ffa7c4', children: 'hi', }, key: null, ref: null, $$typeof: Symbol.for('react.element'), }
إذا كنت قد استخدمت React من قبل ، فقد تكون معتادًا على حقول الكتابة والدعائم والمفتاح والمرجع. ولكن ما هي خاصية $$typeof
؟ ولماذا يكون لها الرمز Symbol()
كقيمة له؟
قبل أن تصبح مكتبات واجهة المستخدم شائعة ، لعرض إدخال العميل في رمز التطبيق ، تم إنشاء سطر يحتوي على علامات HTML وإدراجه مباشرة في DOM ، عبر innerHTML:
const messageEl = document.getElementById('message'); messageEl.innerHTML = '<p>' + message.text + '</p>';
تعمل هذه الآلية بشكل جيد ما لم يتم تعيين message.text
على <img src onerror="stealYourPassword()">
. وفقًا لذلك ، نخلص إلى أنك لست بحاجة إلى تفسير جميع مدخلات العميل على أنها علامة HTML.
للحماية من مثل هذه الهجمات ، يمكنك استخدام واجهات برمجة التطبيقات الآمنة مثل document.createTextNode()
أو textContent
، والتي لا تفسر النص. وكتدبير إضافي ، يمكنك الهروب من السلاسل عن طريق استبدال الأحرف التي يحتمل أن تكون خطرة مثل <
، >
بأخرى آمنة.
ومع ذلك ، فإن احتمال الخطأ كبير ، لأنه من الصعب تتبع جميع الأماكن التي تستخدم فيها المعلومات التي سجلها المستخدم في صفحتك. لهذا السبب تعمل المكتبات الحديثة مثل React بأمان مع أي نص افتراضي:
<p> {message.text} </p>
إذا كانت message.text
عبارة عن سلسلة ضارة بها علامة <img>
، فلن تتحول إلى علامة <img>
حقيقية. React يهرب من محتوى النص ثم يضيفه إلى DOM. لذلك ، بدلاً من رؤية <img>
، يمكنك ببساطة رؤية ترميزها كسلسلة.
لعرض HTML التعسفي داخل عنصر React ، يجب عليك استخدام البناء التالي: dangerouslySetInnerHTML={{ __html: message.text }}
. التصميم غير مريح عن قصد. بسبب العبث ، يصبح أكثر وضوحا ، ويجذب الانتباه عند عرض الكود.
هل هذا يعني أن React آمن تمامًا؟ رقم هناك العديد من أساليب الهجوم المعروفة استنادًا إلى HTML و DOM. سمات العلامة تستحق اهتماما خاصا. على سبيل المثال ، إذا كتبت <a href={user.website}>
، فيمكنك استبدال رمز ضار 'javascript: stealYourPassword()'
نصية: 'javascript: stealYourPassword()'
.
في معظم الحالات ، يكون وجود ثغرات أمنية من جانب العميل نتيجة لمشاكل على جانب الخادم ، ويجب إصلاحها أولاً وقبل كل شيء.
ومع ذلك ، فإن العرض الآمن لمحتوى النص المخصص هو خط الدفاع الأول المعقول الذي يعكس العديد من الهجمات المحتملة.
بناءً على الاعتبارات السابقة ، يمكننا أن نستنتج أن الشفرة التالية يجب أن تكون آمنة تمامًا:
// <p> {message.text} </p>
ولكن هذا ليس هو الحال أيضا. وهنا نقترب من شرح وجود $$typeof
في عنصر React.
كما أوضحنا سابقًا ، فإن عناصر React هي كائنات بسيطة:
{ type: 'marquee', props: { bgcolor: '#ffa7c4', children: 'hi', }, key: null, ref: null, $$typeof: Symbol.for('react.element'), }
عادة ، يتم إنشاء عنصر React.createElement()
عن طريق استدعاء وظيفة React.createElement()
، ولكن يمكنك إنشائها على الفور باستخدام حرفي ، كما فعلت أعلاه.
لنفترض أننا نخزن على الخادم سلسلة أرسلها المستخدم إلينا مسبقًا ، وفي كل مرة نعرضها على جانب العميل. لكن شخصًا ما ، بدلاً من الخيط ، أرسل إلينا JSON:
let expectedTextButGotJSON = { type: 'div', props: { dangerouslySetInnerHTML: { __html: '/* */' }, }, // ... }; let message = { text: expectedTextButGotJSON }; // React 0.13 <p> {message.text} </p>
وهذا هو ، فجأة ، بدلاً من السلسلة المتوقعة ، تحولت قيمة المتغير expectedTextButGotJSON
إلى JSON. التي ستتم معالجتها بواسطة React كحرف ، وبالتالي تنفيذ التعليمات البرمجية الضارة.
يكون React 0.13 عرضة لهجوم يشبه XSS ، ولكن بدءًا من الإصدار 0.14 ، يتم تمييز كل عنصر برمز:
{ type: 'marquee', props: { bgcolor: '#ffa7c4', children: 'hi', }, key: null, ref: null, $$typeof: Symbol.for('react.element'), }
تعمل هذه الحماية لأن الأحرف ليست قيمة JSON صالحة. لذلك ، حتى إذا كان لدى الخادم ثغرة أمنية محتملة وإرجاع JSON بدلاً من النص ، لا يمكن أن يحتوي JSON على Symbol.for('response.element')
. يتحقق React من العنصر element.$$typeof
ويرفض معالجة العنصر إذا كان مفقودًا أو غير صالح.
الميزة الرئيسية لـ Symbol.for()
هي أن الرموز عالمية بين السياقات لأنها تستخدم سجل Symbol.for()
. هذا يضمن نفس قيمة الإرجاع حتى في iframe. وحتى إذا كانت هناك عدة نسخ من React على الصفحة ، فستظل قادرة على "المطابقة" من خلال قيمة واحدة لـ $$typeof
.
ماذا عن المتصفحات التي لا تدعم الشخصيات؟
للأسف ، لن يتمكنوا من تطبيق الحماية الإضافية التي تمت مناقشتها أعلاه ، لكن عناصر React ستظل تحتوي على خاصية $$typeof
للتناسق ، لكنها ستكون مجرد رقم - 0xeac7
.
لماذا بالضبط 0xeac7
؟ لأنه يبدو وكأنه رد فعل.