THREATS

XSS چیست؟ آسیب‌پذیری XSS با مثال ساده و راه‌های واقعی رفع

علیرضا کیا
علیرضا کیا
کارشناس وب و موبایلبازبینی: ۲۵ مرداد ۱۴۰۵
۲۱ خرداد ۱۴۰۵ ۱۷ دقیقه مطالعه

آسیب‌پذیری XSS (Cross-Site Scripting) یعنی مهاجم می‌تواند کاری کند که مرورگرِ کاربران شما، جاوااسکریپتِ او را در دامنه‌ی شما اجرا کند. نتیجه‌اش این است که هر کاری کاربر می‌تواند در برنامه انجام دهد، مهاجم هم می‌تواند — بی‌آنکه رمز عبوری بداند. XSS با شناسه‌ی CWE-79 و بیش از ۳۰٬۰۰۰ CVE ثبت‌شده، رتبه‌ی یکِ فهرست ۲۰۲۵ خطرناک‌ترین ضعف‌های نرم‌افزاری MITRE است و در OWASP Top 10:2025 زیر دسته‌ی A05 تزریق قرار دارد. این مقاله همان چیزی را توضیح می‌دهد که در محتوای فارسی معمولاً غلط نوشته می‌شود: اینکه فریم‌ورک مدرن و هدر CSP، هیچ‌کدام XSS را «تمام‌شده» نمی‌کنند.

در یک نگاه

  • XSS سه نوع دارد: بازتابی (Reflected)، ذخیره‌شده (Stored) و مبتنی بر DOM — و نوع سوم را اسکنرهای سمت سرور معمولاً نمی‌بینند.
  • استفاده از React، Vue یا Angular «پایان XSS» نیست؛ فرار از فیلتر فقط برای درج متن خودکار است و درهای پشتی مثل dangerouslySetInnerHTML و v-html باز می‌مانند.
  • CSP یک کاهش‌دهنده‌ی اثر است، نه راه‌حل. سیاستی که 'unsafe-inline' در script-src دارد، عملاً هیچ محافظتی در برابر XSS نمی‌دهد.
  • سیاست CSP مبتنی بر فهرست میزبان‌های مجاز شکسته است؛ پژوهش گوگل (CCS 2016) نشان داد ۹۴٫۷۲٪ سیاست‌های بررسی‌شده قابل دور زدن بوده‌اند.
  • Trusted Types از فوریه ۲۰۲۶ در وضعیت «Baseline 2026 — تازه در دسترس» است؛ یعنی دیگر مخصوص کروم نیست، اما مرورگرهای قدیمی‌تر آن را اعمال نمی‌کنند.

XSS چیست و در عمل چه اتفاقی می‌افتد؟

هر صفحه‌ی وب ترکیبی از دو چیز است: کد (HTML، جاوااسکریپت) و داده (نام کاربر، متن نظر، مقدار پارامتر جست‌وجو). آسیب‌پذیری XSS وقتی متولد می‌شود که مرزِ این دو مخدوش شود — یعنی داده‌ای که کاربر فرستاده، به‌جای «متن»، به‌عنوان «کد» توسط مرورگر تفسیر شود.

یک مثال عامدانه ساده: صفحه‌ای که عبارت جست‌وجوشده را در پاسخ تکرار می‌کند. اگر برنامه همان مقدار را بدون کدگذاری در HTML بگذارد، مهاجم می‌تواند به‌جای یک واژه، یک تگ بفرستد:

GET /search?q=<img src=x onerror=alert(1)>
<p>نتیجه‌ای برای «<img src=x onerror=alert(1)>» یافت نشد</p>

مرورگر نمی‌داند این تگ از کجا آمده؛ برای او بخشی از صفحه‌ی example.com است و با تمام اختیارات آن دامنه اجرا می‌شود: دسترسی به کوکی‌های غیر HttpOnly، خواندن و نوشتن در DOM، و ارسال درخواست با نشست کاربر و خواندن پاسخ آن.

به همین دلیل ارزیابی «فقط یک alert بالا می‌آید، مهم نیست» غلط است: alert ساده‌ترین راه اثبات اجرای کد است و چیزی که اثبات می‌شود «اجرای جاوااسکریپت دلخواه در مبدأ شما» است. برای مکانیزم دقیق‌تر در سطح مرورگر و زمینه‌های مختلف درج، دانشنامه‌ی XSS را ببینید؛ این مقاله روی فهم و مثال تمرکز دارد.

یک اصطلاح که باید دقیق بماند

XSS درباره‌ی اجرای کد روی سرور نیست؛ کد در مرورگر قربانی اجرا می‌شود. جمله‌ی «مهاجم با XSS سرور را در اختیار می‌گیرد» دقیق نیست، مگر آنکه XSS به زنجیره‌ی دیگری وصل شود — مثل پنلی که امکان ویرایش قالب یا آپلود فایل دارد.

نوع اول: XSS بازتابی (Reflected)

در XSS بازتابی، بدنه‌ی حمله در خودِ درخواست است و در همان پاسخِ بلافاصله بازتاب می‌شود؛ جایی ذخیره نمی‌شود.

مثال رایج: صفحه‌ی خطای پرداخت که پیام خطا را از پارامتر URL می‌خواند تا «پیام سرویس بانکی» را نمایش دهد:

/payment/failed?msg=تراکنش+ناموفق+بود

اگر مقدار msg بدون کدگذاری در HTML قرار بگیرد، مهاجم لینکی می‌سازد که در آن msg حاوی تگ اسکریپت است و آن را برای قربانی می‌فرستد.

محدودیت مهم این نوع: مهاجم باید قربانی را وادار کند لینک ساخته‌شده را باز کند. این یک گام اجتماعی اضافه است و به همین دلیل شدت XSS بازتابی معمولاً از نوع ذخیره‌شده پایین‌تر ارزیابی می‌شود — اما «پایین‌تر» به معنی «کم‌اهمیت» نیست؛ اگر هدف پنل مدیریت باشد و یک لینک برای یک ادمین کافی باشد، شدت واقعی بالا است.

جاهایی که در تست نفوذ وب بیشتر پیدا می‌شود: صفحات خطا و پیام‌های وضعیت که متن را از پارامتر می‌گیرند، صفحه‌ی نتایج جست‌وجو، فرم‌های چندمرحله‌ای که مقدار وارد‌شده را «برای تأیید» نمایش می‌دهند، پارامترهای بازگشت و ردیابی مثل returnUrl و ref، و هدرهایی مثل Referer و User-Agent که در صفحات آماری چاپ می‌شوند. این کلاس یافته اغلب در مسیرهایی است که تیم توسعه آن‌ها را «صفحه‌ی مهمی نیست» می‌داند.

نوع دوم: XSS ذخیره‌شده (Stored) — پرخطرترین نوع

در XSS ذخیره‌شده، بدنه‌ی حمله در سمت سرور ذخیره می‌شود و بعداً به کاربران دیگر سرو می‌شود. هیچ لینک آلوده‌ای لازم نیست؛ قربانی صرفاً صفحه‌ای عادی از سایت شما را باز می‌کند.

سناریوی باورپذیر: کاربری در پنل پشتیبانی تیکتی ثبت می‌کند و در فیلد «نام شرکت» یک تگ اسکریپت می‌گذارد. فرم ثبت، ورودی را با کدگذاری صحیح نشان می‌دهد و ظاهراً همه چیز درست است — اما در پنل کارشناسان همان مقدار در یک جدول HTML بدون کدگذاری چاپ می‌شود. نتیجه: هر کارشناسی که فهرست تیکت‌ها را باز کند، کد مهاجم را با نشست خودش اجرا می‌کند.

این الگو — «ورودی در مسیر A امن است، در مسیر B نیست» — شایع‌ترین علت XSS ذخیره‌شده است. کدگذاری باید در لحظه‌ی خروجی و متناسب با زمینه‌ی همان خروجی انجام شود، نه یک‌بار در لحظه‌ی ورود.

نقاط پرخطر که همیشه باید آزموده شوند

  • نظرات، تیکت‌ها، توضیحات محصول و پروفایل کاربری.
  • نام فایل آپلودشده که در فهرست فایل‌ها چاپ می‌شود، و محتوای فایل SVG؛ SVG یک سند XML اجراپذیر است، نه تصویر بی‌خطر.
  • فیلدهایی که تنها در پنل مدیریت نمایش داده می‌شوند — بحرانی‌ترین حالت، چون قربانی کاربر باامتیاز است.
  • گزارش‌های خروجی HTML و ایمیل‌های قالب‌دار.
  • مقادیری که از سرویس دیگری می‌آیند (CRM، درگاه، پیامک) — داده‌ی «داخلی» هم نامعتمد است.
باور غلط رایج

«ورودی را در لحظه‌ی ثبت پاک‌سازی می‌کنیم، پس XSS نداریم.» پاک‌سازی در ورودی سه مشکل دارد: اول اینکه شما نمی‌دانید این داده بعداً در چه زمینه‌ای (HTML، اتریبیوت، جاوااسکریپت، URL) استفاده می‌شود و هر زمینه کدگذاری متفاوتی می‌خواهد؛ دوم اینکه داده‌ی قدیمیِ پیش از اضافه‌شدن فیلتر، همچنان در پایگاه‌داده نشسته؛ سوم اینکه هر مسیر ورود جدید (API، ایمپورت CSV، سرویس داخلی) این فیلتر را دور می‌زند. جای درست کدگذاری، خروجی است.

نوع سوم: XSS مبتنی بر DOM — و چرا اسکنرها آن را نمی‌بینند

در XSS مبتنی بر DOM، آسیب‌پذیری کاملاً در جاوااسکریپت سمت کلاینت است. سرور هیچ نقشی در بازتاب بدنه‌ی حمله ندارد؛ کد جاوااسکریپتِ خودِ صفحه، مقداری را از یک منبع (Source) تحت کنترل مهاجم می‌خواند و بدون پردازش ایمن، آن را به یک گیرنده‌ی خطرناک (Sink) می‌دهد.

نکته‌ی تعیین‌کننده: پاسخ سرور برای ورودی سالم و مخرب می‌تواند کاملاً یکسان باشد. ابزارهایی که فقط تفاوت پاسخ HTTP را می‌سنجند این کلاس را از دست می‌دهند؛ کشفش به تحلیل جریان داده در جاوااسکریپت نیاز دارد.

رایج‌ترین منبع، خودِ URL از طریق window.location است — رشته‌ی پرس‌وجو، مسیر، و مهم‌تر از همه قطعه (Fragment)، یعنی هرچه بعد از # می‌آید. قطعه هیچ‌گاه به سرور فرستاده نمی‌شود و همین، امضای بسیاری از باگ‌های DOM XSS است — و دلیل اینکه WAF نمی‌تواند جلوی آن را بگیرد.

گیرنده‌های خطرناکی که باید بشناسید

دستهنمونه‌ها
درج HTMLinnerHTML، outerHTML، insertAdjacentHTML، document.write()، document.writeln()
اجرای کدeval()، Function()، setTimeout/setInterval با آرگومان رشته‌ای
مسیر و منبعlocation/location.href (برای طرح javascript:)، script.src، iframe.srcdoc
رخدادهاانتساب مستقیم به element.onevent
jQueryhtml()، append()، after()، prepend()، replaceWith()، wrap()، $.parseHTML()، attr() و خودِ انتخاب‌گر $()

یک نمونه‌ی مینیمال از الگویی که در پروژه‌های واقعی زیاد دیده می‌شود — نمایش «تب فعال» بر اساس قطعه‌ی URL:

// آسیب‌پذیر: منبع = location.hash ، گیرنده = innerHTML
const tab = location.hash.slice(1);
document.getElementById("title").innerHTML = "بخش: " + tab;

// اصلاح‌شده: نوشتن به‌عنوان متن، نه HTML
document.getElementById("title").textContent = "بخش: " + tab;

ابزار مرجع این کلاس، DOM Invader است که داخل مرورگر تعبیه‌شده‌ی Burp Suite جریان منبع‌به‌گیرنده را ردیابی می‌کند — و برخلاف تصور رایج، در نسخه‌ی Community هم موجود است. کنار آن، بازبینی کد منبع بهترین بازده را دارد.

مقایسه‌ی سه نوع در یک نگاه

ویژگیبازتابی (Reflected)ذخیره‌شده (Stored)مبتنی بر DOM
محل بدنه‌ی حملهدرخواست کاربرپایگاه‌داده یا فایل سرورURL (اغلب قطعه)، postMessage، localStorage
نیاز به تعامل قربانیبله — باز کردن لینک ساخته‌شدهخیر — بازدید صفحه‌ی عادی کافی استبله — باز کردن لینک ساخته‌شده
دیده‌شدن در پاسخ سروربلهبلهخیر — پاسخ ممکن است یکسان باشد
قابل کشف با اسکنر خودکارمعمولاً بلهمعمولاً بلهضعیف — نیاز به تحلیل جریان داده
شدت معمولمتوسط تا بالابالا تا بحرانیمتوسط تا بالا

دو اصطلاح دیگر هم در گزارش‌ها دیده می‌شود: Self-XSS که قربانی باید خودش بدنه را وارد کند و به‌تنهایی معمولاً قابل گزارش نیست، و Blind XSS که بدنه در جایی مثل پنل داخلی اجرا می‌شود و مهاجم آن را نمی‌بیند.

باور غلط اصلی: «ما React/Vue/Angular استفاده می‌کنیم، پس XSS نداریم»

باور غلط رایج

«فریم‌ورک ما به‌طور خودکار ورودی را فرار می‌دهد، پس XSS منتفی است.» این جمله نیمه‌درست است. فرار خودکار فقط برای درج متن کار می‌کند: {expr} در JSX و {{expr}} در Vue و Angular گره‌ی متنی می‌سازند، نه نشانه‌گذاری. این واقعاً بخش عمده‌ی XSSهای ساده را از بین می‌برد، اما چند کلاس آسیب‌پذیری دست‌نخورده باقی می‌ماند.

۱) درهای پشتیِ عامدانه

هر فریم‌ورک راهی برای درج HTML خام دارد، چون گاهی لازم است. مستندات خود React درباره‌ی dangerouslySetInnerHTML می‌گوید: «این کار خطرناک است... مگر آنکه نشانه‌گذاری از منبعی کاملاً مورد اعتماد بیاید، ایجاد یک آسیب‌پذیری XSS با این روش trivial است.»

فریم‌ورکراه فرار از فرار خودکار
ReactdangerouslySetInnerHTML
Vueدستور v-html
AngularDomSanitizer.bypassSecurityTrustHtml() و bypassSecurityTrustScript()
Svelte{@html ...}

در بازبینی کد، جست‌وجوی همین چند رشته در کل مخزن سریع‌ترین راه پیدا کردن XSS در یک برنامه‌ی مدرن است.

۲) اتریبیوت‌های URL هیچ محافظتی ندارند

<a href={userInput}> در React در برابر طرح javascript: محافظت‌شده نیست؛ فرار خودکار مربوط به زمینه‌ی HTML است، نه اعتبارسنجی طرح URL. همین موضوع برای src، formaction، data و xlink:href هم برقرار است. طرح URL ورودی کاربر باید با فهرست سفید سنجیده شود: فقط http و https.

۳) تزریق قالب سمت کلاینت

اگر ورودی کاربر به خودِ قالب برسد و نه به داده، فرار خودکار بی‌معنی می‌شود، چون فریم‌ورک پیش از اجرا محتوا را رمزگشایی و ارزیابی می‌کند؛ به بیان PortSwigger، کدگذاری HTML به‌تنهایی کافی نیست. تجربه‌ی تاریخی مهم اینجا AngularJS است: سندباکس آن بارها دور زده شد — بدنه‌ی کلاسیکش 'a'.constructor.prototype.charAt=[].join — و در Angular 1.6 حذف شد، دقیقاً به این دلیل که نگه‌دارندگان نتیجه گرفتند سندباکس یک مرز امنیتی نیست.

۴) رندر سمت سرور، آلودگی پروتوتایپ و mXSS

سه کلاس باقی‌مانده کوتاه اما واقعی‌اند. در رندر سمت سرور و Hydration، وضعیت اولیه‌ی برنامه به‌صورت JSON داخل یک بلوک <script> تزریق می‌شود و قواعد زمینه تغییر می‌کند؛ دنباله‌ی </script> داخل داده کافی است تا بلوک بسته شود. در آلودگی پروتوتایپ (Prototype Pollution)، مهاجم با آلوده کردن پروتوتایپ، یک «گجت» موجود در کتابخانه را به گیرنده‌ی خطرناک تبدیل می‌کند و بدون تزریق مستقیم HTML به اجرای اسکریپت می‌رسد؛ فریم‌ورک‌ها چون پیکربندی را از ادغام آبجکت‌های پیش‌فرض می‌خوانند، مستعد آن‌اند. و mXSS یا XSS جهش‌یافته وقتی رخ می‌دهد که تجزیه‌گر HTML مرورگر، نشانه‌گذاری را متفاوت از آنچه پاک‌ساز تجزیه کرده بود دوباره سریال‌سازی کند — دلیل وجود سابقه‌ی CVE برای DOMPurify و دلیل اینکه پاک‌ساز باید همیشه به‌روز باشد. پاک‌ساز دفاع درجه دو است؛ درجه یک، ندادن HTML خام به کاربر است.

اثر واقعی یک XSS: چرا «فقط alert» نیست

شدت یک XSS در گزارش، بر پایه‌ی همین فهرست تعیین می‌شود:

  • سرقت نشست: اگر کوکی نشست پرچم HttpOnly نداشته باشد، خواندن و ارسال آن به سرور مهاجم کار چند خط کد است.
  • عملیات از طرف کاربر: حتی با HttpOnly، اسکریپت مهاجم می‌تواند درخواست احرازهویت‌شده بفرستد — تغییر ایمیل بازیابی، ساخت کاربر ادمین، تأیید یک تراکنش — چون مرورگر کوکی را خودش ضمیمه می‌کند.
  • دور زدن کامل CSRF: اسکریپت هم‌مبدأ می‌تواند توکن را از صفحه بخواند؛ یعنی XSS تمام دفاع‌های CSRF را بی‌اثر می‌کند.
  • ثبت کلید و فیشینگ درون‌دامنه: شنود ورودی رمز و شماره کارت، یا جایگزینی فرم ورود — با دامنه و قفل TLS معتبر در نوار آدرس.

در فروشگاه‌ها این مستقیماً به سرقت اطلاعات پرداخت مشتری وصل می‌شود (امنیت فروشگاه‌های اینترنتی) و در سایت‌های وردپرسی بیشترین منشأ XSS افزونه‌ها هستند (آسیب‌پذیری‌های وردپرس).

CSP: کاهش اثر است، نه رفع — و اکثر سیاست‌ها کار نمی‌کنند

Content Security Policy هدری است که به مرورگر می‌گوید اجرای اسکریپت از چه منابعی مجاز است. CSP درست، اثر یک XSS را به‌شدت کم می‌کند — اما دو حقیقت را باید صریح گفت.

باور غلط رایج

«CSP گذاشتیم، پس XSS رفع شده است.» خیر. CSP یک کاهش‌دهنده‌ی اثر است، نه اصلاح باگ. آسیب‌پذیری همچنان در کد وجود دارد و در گزارش تست نفوذ باید با شدت خودش ثبت شود. بدتر از آن: سیاستی که 'unsafe-inline' را در script-src دارد، برای XSS عملاً هیچ محافظتی فراهم نمی‌کند — و این رایج‌ترین حالتی است که در عمل دیده می‌شود، چون تیم‌ها برای اینکه اسکریپت‌های درون‌خطی موجود نشکنند، همین را اضافه می‌کنند. 'unsafe-eval' هم گیرنده‌های مبتنی بر eval را باز می‌گذارد.

حقیقت دوم به معماری سیاست مربوط است. رویکرد سنتی CSP «فهرست میزبان‌های مجاز» است؛ پژوهش گوگل با عنوان «CSP Is Dead, Long Live CSP» (کنفرانس CCS 2016) نشان داد این رویکرد ساختاراً شکست‌خورده است:

  • ۹۴٫۷۲٪ از کل سیاست‌های متمایز، راه دور زدن داشتند؛ و ۷۵٫۸۱٪ از سیاست‌های مبتنی بر فهرست مجاز قابل دور زدن بودند.
  • ۹۹٫۳۴٪ از میزبان‌های دارای CSP، سیاستی داشتند که هیچ محافظت واقعی در برابر XSS نمی‌داد.
  • ۱۴ مورد از ۱۵ دامنه‌ای که بیش از همه در فهرست مجاز اسکریپت قرار می‌گیرند، نقطه‌ی ناامن داشتند.

دلیل ساختاری است: هر میزبان مجاز که یک اندپوینت JSONP، نسخه‌ی قدیمی AngularJS یا مسیر سروِ دلخواه فایل داشته باشد، به ابزار اجرای اسکریپت تبدیل می‌شود — و نگهداری چنین فهرستی کاری بی‌پایان است.

سیاست درست در ۲۰۲۶ چه شکلی است؟

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

سه نکته‌ی حیاتی: nonce باید برای هر پاسخ HTTP تازه و رمزنگارانه تصادفی باشد (nonce ثابت بی‌ارزش است)؛ میان‌افزاری که کورکورانه به همه‌ی تگ‌های اسکریپت nonce می‌چسباند خطرناک است، چون اسکریپت تزریق‌شده‌ی مهاجم هم nonce می‌گیرد؛ و 'strict-dynamic' همان چیزی است که این سیاست را در برنامه‌های واقعی با باندلر و لودر قابل استقرار می‌کند. جزئیات کامل، ازجمله حالت Report-Only برای استقرار تدریجی، در دانشنامه‌ی CSP آمده است.

Trusted Types — وضعیت دقیق در ۲۰۲۶

Trusted Types ساختاراً جلوی DOM XSS را می‌گیرد: با آن، انتساب رشته‌ی خام به گیرنده‌های خطرناک اصلاً ممکن نیست و فقط آبجکت‌هایی که یک تابع سیاستِ ثبت‌شده تولید کرده باشد پذیرفته می‌شوند. فعال‌سازی با دو دستور CSP انجام می‌شود: require-trusted-types-for 'script' و trusted-types.

وضعیت پشتیبانی را دقیق بگوییم، چون تازه تغییر کرده: MDN اکنون Trusted Types را «Baseline 2026 — تازه در دسترس، از فوریه ۲۰۲۶» علامت زده است. یعنی جمله‌ی «Trusted Types فقط در کروم کار می‌کند» دیگر درست نیست. اما در جهت مخالف هم نباید مبالغه کرد: مرورگرهای قدیمی‌تری که هنوز در دست کاربران‌اند آن را اعمال نمی‌کنند، پس Trusted Types یک لایه‌ی دفاع در عمق است، نه جانشین کدنویسی درست.

XSS چگونه در تست نفوذ کشف می‌شود؟

روال حرفه‌ای پنج گام دارد و هیچ‌کدام «زدن یک بدنه‌ی آماده در همه‌ی فیلدها» نیست:

  1. نقشه‌برداری نقاط ورود: پارامترها، هدرها، مسیرها، بدنه‌های JSON، پیام‌های postMessage و کلیدهای localStorage. در راهنمای آزمون OWASP، XSS بازتابی و ذخیره‌شده زیر اعتبارسنجی ورودی و DOM XSS زیر تست سمت کلاینت است؛ پوشش کامل با متدولوژی WSTG تعریف می‌شود.
  2. تعیین زمینه‌ی بازتاب: یک رشته‌ی بی‌خطر و یکتا می‌فرستیم و می‌بینیم در چه زمینه‌ای ظاهر می‌شود — متن HTML، مقدار اتریبیوت، بلوک جاوااسکریپت، CSS یا URL. بدنه‌ی مؤثر برای هر زمینه متفاوت است.
  3. سنجش فیلترها: کدام کاراکترها حذف، کدام کدگذاری و کدام دست‌نخورده رد می‌شوند.
  4. اثبات اثر با کمترین تهاجم: یافته با بدنه‌ی اثباتی بی‌ضرر ثبت می‌شود، نه با کدی که داده استخراج کند؛ چارچوبش در گزارش تست نفوذ و سند دامنه‌ی کار مشخص است.
  5. آزمون مجدد پس از رفع — اصلاح‌های XSS مستعد «رفع در یک مسیر و باقی‌ماندن در مسیر دیگر» هستند.

ابزارها کمک می‌کنند اما جای تحلیل را نمی‌گیرند: پروکسی رهگیر برای مشاهده و بازپخش، DOM Invader برای ردیابی جریان در DOM، و اسکنر الگوهای شناخته‌شده — فهرست کامل در ابزارهای بررسی امنیت سایت. اما آنچه یافته‌های DOM XSS و XSS در پنل‌های داخلی را بیرون می‌کشد، خواندن کد و درک جریان داده است.

دفاع درست، به ترتیب اثربخشی

  1. کدگذاری خروجی متناسب با زمینه. در فریم‌ورک‌های مدرن رفتار پیش‌فرض است؛ کار شما خارج نشدن از مسیر پیش‌فرض است.
  2. حذف درهای پشتی. هر مورد dangerouslySetInnerHTML، v-html، bypassSecurityTrust* و {@html} باید توجیه مکتوب داشته باشد؛ یک قاعده‌ی lint که این‌ها را علامت بزند ارزان‌ترین کنترل امنیتی ممکن است.
  3. پاک‌سازی فقط جایی که HTML خام لازم است (ویرایشگر متن غنی)، با پاک‌سازی نگه‌داری‌شده مثل DOMPurify یا Sanitizer API بومی — و به‌روز نگه‌داشتن آن.
  4. اعتبارسنجی طرح URL برای هر اتریبیوت URL: فهرست سفید، نه فهرست سیاه.
  5. CSP سخت‌گیرانه با nonce و 'strict-dynamic'، به‌همراه object-src 'none' و base-uri 'none'؛ اول در حالت Report-Only.
  6. Trusted Types برای بستن ساختاری کلاس DOM XSS.
  7. پرچم‌های کوکی HttpOnly، Secure و SameSite — که باگ را رفع نمی‌کنند، اثرش را محدود می‌کنند.
  8. هرگز WAF را به‌عنوان راهکار رفع ثبت نکنید. در DOM XSS که بدنه در قطعه‌ی URL است، درخواست هرگز به سرور نمی‌رسد.

در سطح فرایند دو کار بیشترین بازده را دارد: افزودن SAST و DAST به خط لوله‌ی CI/CD طبق توصیه‌ی OWASP برای دسته‌ی A05:2025 تزریق، و آموزش تیم توسعه — چون XSS تقریباً همیشه یک الگوی کدنویسی تکرارشونده است. برای سنجش وضعیت فعلی، تست نفوذ وب هر مسیر را با اثبات اثر و راهکار مشخص بررسی می‌کند و مشاوره‌ی امنیتی گزینه‌ی سبک‌تر برای بازبینی معماری پیش از انتشار است.

پرسش‌های متداول

آسیب‌پذیری XSS چیست و چه خطری دارد؟

XSS یعنی مهاجم می‌تواند جاوااسکریپت دلخواه خود را در مرورگر کاربران شما و در مبدأ دامنه‌ی شما اجرا کند. خطر واقعی‌اش سرقت نشست، انجام عملیات از طرف کاربر، خواندن توکن CSRF و فیشینگ درون دامنه‌ی معتبر است. شناسه‌ی استاندارد آن CWE-79 و جایگاهش در OWASP Top 10:2025 زیر دسته‌ی A05 تزریق است.

تفاوت XSS بازتابی و ذخیره‌شده چیست؟

در نوع بازتابی، بدنه‌ی حمله در خودِ درخواست است و مهاجم باید قربانی را وادار کند لینک ساخته‌شده را باز کند. در نوع ذخیره‌شده، بدنه در سرور ذخیره می‌شود و به کاربران دیگر سرو می‌شود؛ قربانی فقط باید صفحه‌ای عادی از سایت را ببیند. به همین دلیل شدت نوع ذخیره‌شده معمولاً بالاتر است، خصوصاً وقتی صفحه‌ی هدف پنل مدیریت باشد.

آیا استفاده از React یا Vue جلوی XSS را می‌گیرد؟

نه به‌طور کامل. فرار خودکار فقط برای درج متن است. dangerouslySetInnerHTML، v-html، bypassSecurityTrustHtml و {@html} همچنان درِ باز هستند، href با مقدار کاربر در برابر javascript: محافظت‌شده نیست، و تزریق قالب سمت کلاینت و آلودگی پروتوتایپ کلاس‌های مستقلی‌اند. مستندات خود React می‌گوید ایجاد XSS با این روش «trivial» است.

آیا CSP جلوی XSS را می‌گیرد؟

CSP اثر XSS را کم می‌کند اما باگ را رفع نمی‌کند و در گزارش امنیتی نباید به‌عنوان راهکار رفع ثبت شود. اگر سیاست شما 'unsafe-inline' در script-src دارد، عملاً هیچ محافظتی در برابر XSS نمی‌دهد. سیاست‌های مبتنی بر فهرست میزبان مجاز هم شکسته‌اند؛ در پژوهش گوگل ۹۴٫۷۲٪ سیاست‌ها راه دور زدن داشتند. سیاست درست بر پایه‌ی nonce تازه در هر پاسخ به‌همراه 'strict-dynamic' است.

چطور بفهمم سایت من XSS دارد؟

اسکنر خودکار بخشی از موارد بازتابی و ذخیره‌شده را پیدا می‌کند اما DOM XSS را معمولاً از دست می‌دهد، چون پاسخ سرور برای ورودی سالم و مخرب یکسان است. روش قابل اعتماد ترکیب سه کار است: نقشه‌برداری کامل نقاط ورود، تحلیل جریان منبع‌به‌گیرنده در جاوااسکریپت، و بازبینی کد برای درج HTML خام. بررسی امنیت سایت نقطه‌ی شروع مناسبی است.

آیا HttpOnly روی کوکی، XSS را بی‌خطر می‌کند؟

خیر. HttpOnly فقط یک مسیر اثر را می‌بندد: خواندن کوکی با جاوااسکریپت. اسکریپت مهاجم همچنان می‌تواند درخواست احرازهویت‌شده بفرستد، توکن CSRF را بخواند، فرم ورود را جایگزین کند و ورودی‌ها را شنود کند. یک کاهش‌دهنده‌ی مهم است، نه رفع.

علیرضا کیا
WRITTEN BY

علیرضا کیا

کارشناس وب و موبایل

متخصص تست نفوذ وب و اندروید با سابقه‌ی مدیریت تیم نفوذ در داتین. مهندسی معکوس اپ‌ها و کشف ذخیره‌سازی ناامن، تخصص اوست.

THREATS

تزریق SQL به زبان ساده: تشخیص و رفع

ادامه مطلب ←
BASICS

آسیب‌پذیری‌های تحت وب: چک‌لیست کامل OWASP Top 10

ادامه مطلب ←
WEB

امنیت وب اپلیکیشن؛ راهنمای جامع بر پایه OWASP

ادامه مطلب ←