VULN · آسیب‌پذیری‌ها تزریق سمت کلاینت بالا

XSS — اسکریپت‌نویسی میان‌سایتی

Cross-Site Scripting

XSS نقصی است که به مهاجم اجازه می‌دهد کد جاوااسکریپت دلخواه را در مرورگر قربانی و در بستر اعتماد سایت هدف اجرا کند.

تیم فنی پی‌هانتر

XSS چیست و در سطح فنی چه اتفاقی می‌افتد؟

اسکریپت‌نویسی بین‌سایتی (Cross-Site Scripting) که به‌اختصار XSS خوانده می‌شود، دسته‌ای از تزریق است که در آن ورودیِ تحت کنترل مهاجم بدون خنثی‌سازی درست، وارد خروجی HTML یا بستر اجرای جاوااسکریپت یک صفحه می‌شود و مرورگر قربانی آن را به‌عنوان کد اجرا می‌کند. کلید ماجرا این است که کد در مبدأ (origin) سایت هدف اجرا می‌شود؛ یعنی مهاجم به کوکی نشست، توکن‌ها، محتوای صفحه و هر عملیاتی که کاربر مجاز به انجام آن است دسترسی پیدا می‌کند.

مکانیزم دقیق سه گام دارد: یک منبع (source) که داده‌ی مهاجم از آن وارد می‌شود (پارامتر URL، بدنه، هدر یا فرگمنت آدرس)، یک مسیر جریان داده، و یک سینک (sink) خطرناک که داده در آن به‌عنوان markup یا کد تفسیر می‌شود. اگر بین منبع و سینک کدگذاری متناسب با بستر انجام نشود، XSS رخ می‌دهد؛ پس XSS مسئله‌ی «کدگذاری خروجی» است، نه صرفاً «فیلتر ورودی».

اهمیت این آسیب‌پذیری در مقیاس آن است: CWE-79 با بیش از ۳۰٬۰۰۰ CVE ثبت‌شده، پرحجم‌ترین ضعف در دسته‌ی تزریق است و در OWASP Top 10:2025 رتبه‌ی نخست فهرست ۲۵‌گانه‌ی CWE را نیز در اختیار دارد.

سه نوع XSS: Reflected، Stored و DOM

سه دسته‌بندی کلاسیک همچنان معتبرند و تفاوتشان در محل تزریق و ماندگاری بار است:

  • بازتابی (Reflected): بار مخرب داخل خودِ درخواست است و بی‌درنگ در پاسخ همان درخواست بازتاب می‌شود. برای بهره‌برداری باید قربانی را به کلیک روی یک درخواست دستکاری‌شده واداشت (مثلاً از طریق لینک آلوده).
  • ذخیره‌شده (Stored): بار در سمت سرور ذخیره می‌شود (نظر، پروفایل، پیام) و بعداً برای کاربران دیگر سرو می‌شود. بیشترین شدت را دارد، چون بدون تعامل مستقیم به همه‌ی بازدیدکنندگان می‌رسد.
  • مبتنی بر DOM: آسیب‌پذیری کاملاً در جاوااسکریپت سمت کلاینت است. نکته‌ی تعیین‌کننده این است که پاسخ سرور برای ورودی سالم و مخرب می‌تواند یکسان باشد، و رایج‌ترین منبع، فرگمنت آدرس (پس از #) است که هرگز به سرور ارسال نمی‌شود — به همین دلیل اسکنرهای سمت‌سرور و بسیاری از WAFها آن را نمی‌بینند.

سینک‌های پرتکرار عبارت‌اند از innerHTML، insertAdjacentHTML، document.write()، صفت‌های onevent، و در سمت اجرای کد eval() و setTimeout با آرگومان رشته‌ای. در jQuery نیز متدهایی مثل html() و خودِ سلکتور $() سینک محسوب می‌شوند.

آیا React و Angular و Vue جلوی XSS را می‌گیرند؟

فریم‌ورک‌های مدرن مقدار متنیِ درون‌ریزی‌شده را به‌صورت پیش‌فرض کدگذاری می‌کنند؛ {expr} در JSX و {{expr}} در Vue و Angular گره‌ی متنی تولید می‌کنند نه markup. این واقعاً بخش بزرگی از XSSهای ساده را حذف می‌کند — اما آن را ریشه‌کن نمی‌کند.

باور غلط رایج

«ما از React/Angular/Vue استفاده می‌کنیم پس XSS نداریم.» نادرست است. کدگذاری خودکار فقط بستر متنی را پوشش می‌دهد. کلاس‌های باقی‌مانده که مکرراً دیده می‌شوند: راه‌های فرار صریح مانند dangerouslySetInnerHTML در React، v-html در Vue، bypassSecurityTrustHtml در Angular و {@html} در Svelte؛ سینک‌های مبتنی بر اسکیم مثل <a href={userInput}> که در برابر javascript: محافظت نمی‌شود (کدگذاری HTML اسکیم URL را اعتبارسنجی نمی‌کند)؛ تزریق قالب سمت کلاینت (CSTI)؛ رندر سمت سرور و hydration دادهٔ خنثی‌نشده؛ و زنجیره‌های آلودگی پروتوتایپ که به XSS می‌رسند. مستند رسمی React خودش می‌گوید با این روش «به‌سادگی می‌توان یک آسیب‌پذیری XSS ایجاد کرد».

نکته‌ی تاریخی مهم: سندباکس AngularJS هرگز مرز امنیتی نبود و در Angular 1.6 به همین دلیل حذف شد. اگر ورودی کاربر به خودِ قالب برسد (نه به داده)، کدگذاری بی‌فایده است چون فریم‌ورک نحو قالب را خودش رمزگشایی و اجرا می‌کند؛ جزئیات را در تزریق قالب ببینید.

سناریوی واقعی و کشف در تست نفوذ

سناریوی متعارف: بخش نظرات یک فروشگاه ورودی کاربر را بدون کدگذاری نمایش می‌دهد. مهاجم نظری حاوی <script> ثبت می‌کند که کوکی نشست هر بازدیدکننده را به سرور او می‌فرستد؛ چون کوکی HttpOnly ندارد، نشست مدیر ربوده می‌شود — یک XSS ذخیره‌شده با تأثیر بالا.

روش کشف در تست نفوذ وب ترکیبی از دستی و ابزاری است:

  • نگاشت منبع تا سینک: با DevTools مرورگر، مسیر داده از منبع (پارامتر، فرگمنت) تا سینک را دنبال و نقطه‌ی کدگذاری‌نشده را پیدا می‌کنیم.
  • پروب کاناری: یک رشته‌ی یکتا تزریق می‌کنیم و بستر بازتاب آن (HTML، صفت، جاوااسکریپت، URL) و نوع کدگذاری را می‌سنجیم.
  • ابزار: افزونه‌ی DOM Invader در مرورگر داخلی Burp Suite — که در نسخه‌ی Community هم هست — کاناری را در ده‌ها سینک ردیابی می‌کند و شکار XSS مبتنی بر DOM را بسیار سریع‌تر می‌کند. برای نوع بازتابی و ذخیره‌شده، اسکنر Burp و بازبینی دستی مکمل یکدیگرند.

هر یافته با یک بار اثبات مفهوم غیرمخرب مستند می‌شود.

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

نمونه‌ی فنی

یک XSS مبتنی بر DOM از نوع فرگمنت، که چون در URL بعد از # است اصلاً به سرور نمی‌رسد:

// منبع: فرگمنت آدرس، هرگز به سرور ارسال نمی‌شود
const q = location.hash.slice(1);

// سینک خطرناک: نوشتن مستقیم markup در DOM
document.getElementById('out').innerHTML = q;

// شکل بار آزمایشی (اثبات مفهوم):
// #<img src=x onerror=alert(document.domain)>

راه‌حل درست، جایگزینی سینک با گره‌ی متنی است:

// امن: به‌عنوان متن درج می‌شود، نه markup
document.getElementById('out').textContent = q;

دفاع مدرن: CSP و Trusted Types

پشته‌ی دفاعی XSS چندلایه است: لایه‌ی اول کدگذاری متناسب با بستر (پیش‌فرض فریم‌ورک) و پرهیز از راه‌های فرار؛ لایه‌ی دوم یک CSP سخت مبتنی بر nonce با strict-dynamic (سیاست‌های مبتنی بر فهرست سفیدِ دامنه به‌طور ساختاری قابل دور زدن‌اند)؛ و لایه‌ی ساختاری Trusted Types که تخصیص رشته‌ی خام به سینک‌های تزریق را در سطح مرورگر ممنوع می‌کند و با دستور require-trusted-types-for 'script' فعال می‌شود.

وضعیت پشتیبانی Trusted Types

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

ارتباط با OWASP و CWE

XSS شناسه‌ی CWE-79 (Improper Neutralization of Input During Web Page Generation) را دارد. در OWASP Top 10:2025، این ضعف زیر دسته‌ی A05:2025 تزریق (Injection) قرار می‌گیرد — همراه با SQL، NoSQL، دستور سیستم‌عامل و LDAP. در فهرست ۲۵‌گانه‌ی CWE سال ۲۰۲۵، همین ضعف با امتیاز ۶۰٫۳۸ در رتبه‌ی یک ایستاده است. برای مرور کامل دسته‌بندی ریسک به صفحه‌ی OWASP و مقاله‌ی XSS به زبان ساده مراجعه کنید.

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

خطرناک‌ترین نوع XSS کدام است؟

XSS ذخیره‌شده (Stored) معمولاً بیشترین شدت را دارد، چون بار مخرب در سرور می‌ماند و بدون نیاز به تعامل خاص به همه‌ی بازدیدکنندگان — از جمله مدیران — سرو می‌شود. XSS مبتنی بر DOM از نظر کشف دشوارتر است چون تماماً در سمت کلاینت رخ می‌دهد.

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

نه به‌طور کامل. React مقدار متنی را کدگذاری می‌کند، اما dangerouslySetInnerHTML، href با اسکیم javascript:، تزریق قالب سمت کلاینت، و رندر سمت سرورِ داده‌ی خنثی‌نشده همچنان می‌توانند به XSS منجر شوند.

آیا Trusted Types فقط در کروم کار می‌کند؟

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

آیا CSP به‌تنهایی XSS را رفع می‌کند؟

خیر. CSP یک لایه‌ی کاهش تأثیر است، نه رفع ریشه‌ای. یک سیاست با 'unsafe-inline' در script-src عملاً محافظتی نمی‌دهد و CSP در برابر بخشی از XSSهای مبتنی بر DOM هم ناتوان است؛ آنجا Trusted Types لازم می‌شود.

پیشگیری / رفع

راهکارها به ترتیب اثربخشی:

  1. کدگذاری خروجی متناسب با بستر را به فریم‌ورک بسپارید و از راه‌های فرار (dangerouslySetInnerHTML، v-html، bypassSecurityTrust*) پرهیز کنید.
  2. هرجا markup پویا اجتناب‌ناپذیر است، با یک سنیتایزر نگه‌داری‌شده (DOMPurify یا Sanitizer API بومی) پاک‌سازی کنید و آن را به‌روز نگه دارید (به سابقه‌ی mXSS توجه کنید).
  3. اسکیم URL را روی هر صفت URL‌محور اعتبارسنجی کنید تا javascript: مسدود شود.
  4. یک CSP سخت مبتنی بر nonce با strict-dynamic و object-src 'none' و base-uri 'none' مستقر کنید.
  5. Trusted Types را برای حذف ساختاری XSS مبتنی بر DOM فعال کنید.
  6. پرچم HttpOnly روی کوکی نشست، تأثیر سرقت نشست را محدود می‌کند.

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

→ بازگشت به دانشنامه
PUT IT TO THE TEST

امنیت سامانه‌ی شما را
به مهاجمان واگذار نکنید

تیم پی‌هانتر با دیدِ یک مهاجم واقعی، این آسیب‌پذیری‌ها و ده‌ها مورد دیگر را روی دارایی‌های شما می‌سنجد.