كيف تحدث XSS عبر JSON
الخلاصة: JSON لا يُنفَّذ بنفسه أبدًا. تحدث XSS في اللحظة التي تتعامل فيها معه كـHTML — بدمج السلسلة على الخادم، أو innerHTML في الواجهة، أو إدراج JSON مباشرة داخل وسم script.

الخلاصة: JSON لا يُنفَّذ بنفسه أبدًا — أغلق مسارات المخرج الثلاثة أولًا: هرّب على الخادم بحسب السياق، واستخدم textContent بدلًا من innerHTML، واستبدل < بـ\u003c عند إدراج JSON.
{"name": "<img src=x onerror=alert(1)>"} بيانات صالحة. وكونها خطرة أم لا يتوقف كليًا على مكان وضعها.
المدخل الأول: بناء السلسلة على الخادم
res.send(`<div>Hello, ${data.name}</div>`);
يحلل المتصفح الوسوم داخل data.name. JSON هنا مجرد الحاوية؛ والخطأ هو الهروب لسياق خاطئ.
الإصلاح: استخدم الهروب الافتراضي لمحرك القوالب (<%= %> في EJS، و{{ }} في Vue وDjango). وتجنب <%- %> وv-html و|safe لأي شيء يتحكم فيه المستخدم.
المدخل الثاني: innerHTML
el.innerHTML = user.bio; // parses markup
el.textContent = user.bio; // inserts text
{user.bio} في React و{{ user.bio }} في Vue ينشئان عقد نصية وهما آمنان افتراضيًا. أما dangerouslySetInnerHTML وv-html فانسحاب صريح.
إن كنت تحتاج فعلًا إلى نص منسق، فنقّه بقائمة سماح (DOMPurify وما شابه) بدلًا من الثقة بالخادم.
المدخل الثالث: الإدراج داخل وسم script
<script>
window.__DATA__ = {"name": "x</script><script>alert(1)</script>"};
</script>
</script> داخل البيانات يغلق الكتلة مبكرًا وكل ما بعده يُنفَّذ كسكربت جديد. هذه الحالة الأكثر تفويتًا.
JSON.stringify(data)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026');
JSON صالح ما يزال، مطابق بعد التحليل، وغير قادر بعد الآن على إغلاق الوسم. ويفعل Next.js وNuxt الشيء نفسه مع __NEXT_DATA__ و__NUXT__.
جاران يستحسن معرفتهما
اختطاف JSON — كانت المتصفحات القديمة تنفذ JSON المحمّل عبر <script src>؛ وإعادة تعريف Array سرّبت المحتويات. أصلحتها المتصفحات الحديثة، وContent-Type: application/json مع X-Content-Type-Options: nosniff يغلق الباقي.
بقايا JSONP — اسم دالة رد اتصال يتحكم فيه المستخدم هو تنفيذ كود اعتباطي بالتصميم. لا تستخدمه في أنظمة جديدة.
قائمة التحقق
- كل مكان يصل فيه حقل JSON إلى HTML يُهرَّب لسياقه (HTML، والسمة، وJS، وURL)
- لا
innerHTMLولاv-htmlولاdangerouslySetInnerHTMLتستهلك سلاسل API - JSON المضمّن يهرّب
<و>و& - النص المنسق يمر عبر تنقية بقائمة سماح، لا تصفية بقائمة حظر
- الاستجابات تحمل
Content-Type: application/jsonوnosniff
الأسئلة الشائعة
لماذا يمكن أن يثير JSON صالح XSS رغم ذلك؟
JSON مجرد نص ولا يُنفَّذ أبدًا؛ وكونه صالحًا يعني فقط أنه يُحلَّل. تظهر الخطورة عندما يُكتب في سياق HTML أو script: فيحلل المتصفح الوسوم، أو يغلق </script> الكتلة مبكرًا فيُشغَّل الباقي ككود. والخطأ هو غياب الهروب الواعي بالسياق عند الإخراج، لا تنسيق البيانات.
هل أستخدم textContent أم innerHTML لعرض حقول API؟
رجّح textContent: فهو يدرج القيمة كنص عادي، فتُعرض الوسوم حرفيًا. أما innerHTML فيحلل الترميز، فيُشغَّل حقل API يحتوي على <img onerror=...>. و{user.bio} في React و{{ user.bio }} في Vue ينشئان عقد نصية افتراضيًا؛ وdangerouslySetInnerHTML وv-html انسحاب صريح.
كيف أهرّب JSON المضمّن داخل وسم <script>؟
نعم. </script> داخل البيانات يغلق الكتلة مبكرًا وكل ما بعده يُنفَّذ كسكربت جديد. بعد التسلسل، استبدل < و> و& بـ\u003c و\u003e و\u0026: فتبقى النتيجة JSON صالحًا، ويُحلَّل بالطريقة نفسها، ولا يستطيع بعد الآن إغلاق الوسم.