XSS چگونه از طریق JSON رخ میدهد
جمعبندی: JSON هرگز خودش اجرا نمیشود. XSS در لحظهای رخ میدهد که آن را HTML میپندارید — الحاق رشته در سرور، innerHTML فرانتاند، یا جاسازی مستقیم JSON در تگ اسکریپت.

جمعبندی: JSON هرگز خودش اجرا نمیشود — اول سه مسیر خروجی را ببندید: در سرور بر اساس بافت escape کنید، از textContent بهجای innerHTML استفاده کنید، و هنگام جاسازی JSON < را با \u003c جایگزین کنید.
{"name": "<img src=x onerror=alert(1)>"} داده معتبر است. خطرناک بودنش کاملاً به این بستگی دارد که کجا میگذاریدش.
ورود اول: ساخت رشته در سرور
res.send(`<div>Hello, ${data.name}</div>`);
مرورگر تگهای داخل data.name را تجزیه میکند. JSON اینجا فقط ظرف است؛ خطا escape برای بافت اشتباه است.
اصلاح: از escape پیشفرض موتور قالب استفاده کنید (<%= %> در 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 انصراف صریحاند.
اگر واقعاً متن غنی لازم دارید، با allowlist (DOMPurify و مشابه) پاکسازی کنید نه اینکه به بکاند اعتماد کنید.
ورود سوم: جاسازی در تگ اسکریپت
<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 hijacking — مرورگرهای قدیمی JSON بارگذاریشده با <script src> را اجرا میکردند؛ بازتعریف Array محتوا را نشت میداد. مرورگرهای مدرن رفعش کردهاند و Content-Type: application/json بهعلاوه X-Content-Type-Options: nosniff بقیه را میبندد.
باقیماندههای JSONP — نام callback تحت کنترل کاربر بهطراحی اجرای کد دلخواه است. در سیستمهای جدید استفاده نکنید.
چکلیست
- هر جا فیلد JSON به HTML میرسد بر اساس بافتش (HTML، ویژگی، JS، URL) escape میشود
- هیچ
innerHTML،v-htmlیاdangerouslySetInnerHTMLرشته API مصرف نمیکند - JSON جاسازیشده
<،>و&را escape میکند - متن غنی از پاکسازی allowlist عبور میکند، نه فیلتر blacklist
- پاسخها
Content-Type: application/jsonوnosniffدارند
پرسشهای متداول
چرا JSON معتبر همچنان میتواند XSS تحریک کند؟
JSON فقط متن است و هرگز اجرا نمیشود؛ معتبر بودن فقط یعنی تجزیه میشود. خطر وقتی ظاهر میشود که در بافت HTML یا اسکریپت نوشته شود: مرورگر تگها را تجزیه میکند، یا </script> بلوک را زودتر میبندد و بقیه بهعنوان کد اجرا میشود. خطا نبود escape آگاه به بافت در خروجی است، نه قالب داده.
برای رندر فیلدهای API از textContent استفاده کنم یا innerHTML؟
textContent را ترجیح دهید: مقدار را بهصورت متن ساده درج میکند، پس تگها عیناً نمایش داده میشوند. innerHTML نشانهگذاری را تجزیه میکند، پس فیلد API حاوی <img onerror=...> اجرا میشود. {user.bio} در React و {{ user.bio }} در Vue بهصورت پیشفرض گره متن میسازند؛ dangerouslySetInnerHTML و v-html انصراف صریحاند.
JSON جاسازیشده در تگ <script> را چگونه escape کنم؟
بله. </script> داخل داده بلوک را زودتر میبندد و هر چیزی بعدش بهعنوان اسکریپت جدید اجرا میشود. پس از سریالایز، <، > و & را با \u003c، \u003e و \u0026 جایگزین کنید: نتیجه همچنان JSON معتبر است، یکسان تجزیه میشود و دیگر نمیتواند تگ را ببندد.