إن استطاع زائرٌ أن يضع على صفحتك نصًّا يراه زوّارٌ آخرون، ولم يكن ذلك النصّ مُهرَّبًا، فبوسعه أن يضع سكربتًا مكانه. فيعمل بصفة موقعك، وبجلسة زائرك، ويستطيع كلَّ ما يستطيعه ذلك الزائر.
ومن أين يأتي
- تعليقٌ أو اسمٌ أو مراجعة — وكلُّ ما يكتبه واحدٌ ويُعرَض على آخر.
- ووسيطٌ في العنوان يُطبَع ثانيةً على الصفحة، وهو صندوقُ البحث في كلّ موقعٍ تقريبًا.
- واسمُ ملفٍّ مرفوع.
- وبياناتٌ من واجهةٍ برمجيّة لا تملك أمرها.
والعادة
هرِّب عند الإخراج، في كلّ مرّة، وفي السياق الذي تطبع فيه. لا عند الإدخال — فالبيانةُ الواحدة قد تُطبَع في HTML وفي خاصّيّةٍ وفي JavaScript، ولكلٍّ تهريبٌ يخصّه.
echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
- في نصّ HTML — htmlspecialchars مع ENT_QUOTES.
- في خاصّيّة — الشيءُ نفسه، وضع الخاصّيّةَ بين علامتَي اقتباس دائمًا.
- في JavaScript — json_encode، ولا تجمع النصوصَ جمعًا أبدًا.
- في عنوان — urlencode.
والتهريبُ عند الحفظ هو الغلطُ الكلاسيكيّ: إذ تُضطَرّ بعده إلى فكّ التهريب لبريدٍ أو تصديرٍ أو واجهةٍ برمجيّة، وأحدُ تلك الطرق ينسى دائمًا. فاحفظ ما كُتب، وهرِّب عند العرض.
وطبقةٌ ثانية
add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
فسياسةُ أمن المحتوى تُملي على المتصفّح ألّا يشغّل السكربتات إلّا من أصلك أنت، فلا يُنفَّذ سكربتٌ مضمَّنٌ محقونٌ ولو نفذ واحد. وأدخِلها في وضع الإبلاغ فقط أوّلًا، وإلّا كسرتَ صفحتك بنفسك.
واختبره
ضع علامةً لا تضرّ في كلّ حقل، ثمّ انظر مصدرَ الصفحة التي تعرضها. فرؤيتُها نصًّا هي الصواب، ورؤيتُها وسمًا هي العلّة.
ومحرّكُ قوالبَ يهرِّب افتراضيًّا يزيل أكثرَ هذا الصنف من العلل. فإن كان محرّكك كذلك، فلا تمُدّ يدك إلى أداة «الطباعة الخام» بلا سببٍ تستطيع الدفاعَ عنه.
