درس آموزشی درس ۳ از ۵

حمله XSS چیست؟ انواع XSS و راه‌های جلوگیری با مثال

چطور یک متن ساده می‌تونه توی مرورگر به کد تبدیل بشه؟ با داستان آموزشگاه موسیقی، سه نوع XSS و روش جلوگیری از هک سایت رو با مثال‌های امن یاد بگیر.

مقدماتی ۲۸ دقیقه مطالعه بروزرسانی ۲۵ مهر ۱۴۰۵
فهرست این درس

ببین، فرض کن یه آموزشگاه موسیقی سایت داره و هنرجوها از طریق فرم درخواست تعمیر ساز می‌فرستن. یکی می‌نویسه «سیم گیتارم پاره شده» و مسئول آموزشگاه هم اون پیام رو توی پنل می‌خونه. تا اینجا همه‌چیز عادیه؛ ولی مرورگر مسئول از کجا می‌فهمه این ورودی فقط یه متن معمولیه، نه چیزی که باید اجراش کنه؟ اگه برنامه متن کاربر رو با روش اشتباهی وارد صفحه کنه، ممکنه زمینه برای حمله XSS فراهم بشه. کل ماجرا از همین مرز ظاهراً ساده شروع می‌شه.

توی درس قبلی درباره SQL Injection صحبت کردیم؛ اونجا مشکل این بود که داده می‌تونست ساختار دستور دیتابیس رو تغییر بده. این بار قضیه توی مرورگر اتفاق می‌افته. قراره با هم یاد بگیریم حمله XSS چیست، چه فرقی با تزریق SQL داره، انواع XSS کدومن و برای جلوگیری از XSS باید کجای برنامه رو درست کنیم. برای اینکه موضوع ملموس باشه، یه فایل HTML کاملاً محلی درست می‌کنیم؛ نه سراغ سایت کسی می‌ریم و نه داده واقعی جابه‌جا می‌کنیم.

حمله XSS چیست؟ تعریف ساده با یک مثال واقعی‌نما

حمله XSS مخفف Cross-Site Scripting و یکی از آسیب‌پذیری‌های امنیت وبه. به زبان خودمونی، ماجرا وقتیه که یه برنامه، داده‌ای رو که از منبع غیرقابل‌اعتماد گرفته، توی جای نامناسب قرار می‌ده و مرورگر ممکنه اون رو به‌عنوان کد اجرا کنه. در این حالت شخصی که باید فقط حق نوشتن یک پیام رو داشته باشه، ممکنه بتونه روی رفتار صفحه‌ای که کاربر دیگه می‌بینه اثر بذاره. البته وجود متن HTML داخل یه فیلد به‌تنهایی ثابت نمی‌کنه آسیب‌پذیری XSS داریم.

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

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

حمله XSS

چرا اسمش Cross-Site Scripting است؟

اسم Cross-Site Scripting یا همون «اسکریپت‌نویسی بین‌سایتی» از تاریخچه این نوع حمله‌ها اومده، ولی لازم نیست حتماً داده بین دو سایت جابه‌جا بشه تا حمله XSS داشته باشیم. نکته اینه که کد ناخواسته می‌تونه در زمینه امنیتی یک وب‌سایت اجرا بشه و مرورگر اون رو به همون صفحه نسبت بده. برای کوتاه‌نویسی هم از XSS استفاده می‌کنیم تا با CSS، یعنی زبان استایل‌دهی صفحات، اشتباه نشه.

پس موقع آموزش XSS، فقط ترجمه اسمش رو حفظ نکن. بهتره بفهمی HTML صفحه چطور ساخته می‌شه، DOM چه نقشی داره و JavaScript چطوری محتوا رو تغییر می‌ده. وقتی این سه تا رو بفهمی، هم تشخیص ضعف برات راحت‌تر می‌شه، هم می‌فهمی کدوم اصلاح واقعاً ریشه مشکل رو می‌بنده.

یک سناریوی اختصاصی : آموزشگاه موسیقی و یادداشت تعمیر ساز

حالا بریم سراغ آموزشگاه فرضی‌مون. یه فرم داریم که هنرجو داخلش درخواست تعمیر ساز ثبت می‌کنه؛ پیامش روی سرور ذخیره می‌شه و بعداً در پنل مسئول آموزشگاه نشون داده می‌شه. مسئول مجازه متن رو بخونه، ولی طبیعتاً قرار نیست هر چیزی که هنرجو تایپ کرده به دستور اجرایی مرورگر تبدیل بشه. همین تفاوت بین «خواندن پیام» و «اجرای محتوای پیام»، مثال خوبی برای فهم حمله XSS و مخصوصاً نوع ذخیره‌شده اونه.

مرحلهرفتار عادیپرسش امنیتی
ثبت درخواستهنرجو یادداشت تعمیر ساز رو می‌نویسهآیا متن به‌عنوان داده پذیرفته می‌شه؟
ذخیرهسرور متن رو برای نمایش بعدی نگه می‌دارهآیا ذخیره شدن به معنی امن بودن نمایش است؟
نمایش در پنلمسئول یادداشت رو می‌خونهآیا مرورگر متن رو می‌بینه یا HTML اجرایی؟
ویرایش درخواستمسئول وضعیت تعمیر رو تغییر می‌دهآیا داده بیرونی می‌تونه روی رابط پنل اثر بذاره؟
پاسخ به هنرجوسیستم متن پاسخ رو نمایش می‌دهآیا خروجی متناسب با مقصد امن‌سازی شده؟

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

حمله XSS در مرورگر از کجا شروع می‌شه؟

مرورگر وقتی HTML دریافت می‌کنه، ازش ساختاری به اسم DOM می‌سازه. کد JavaScript هم می‌تونه با این ساختار کار کنه؛ مثلاً یه جمله رو عوض کنه یا یه عنصر جدید بسازه. ما دو روش متفاوت برای گذاشتن محتوا داریم: innerHTML به مرورگر می‌گه رشته رو مثل HTML تفسیر کن، ولی textContent برای نمایش متن داخل عناصر معمولی ساخته شده. خیلی از سناریوهای حمله XSS از نوع DOM Based از اشتباه گرفتن همین دو کاربرد شروع می‌شن.

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

آموزش XSS با آزمایش اول؛ تفاوت innerHTML و textContent

برای شروع، یه فایل به اسم xss-lab.html بساز و کد زیر رو داخلش بذار. لازم نیست وب‌سرور نصب کنی؛ فایل رو مستقیم توی مرورگر باز کن. این مثال فقط یه یادداشت فرضی داره و هیچ اطلاعاتی از جایی دریافت یا به جایی ارسال نمی‌کنه. هدفمون اینه که تفاوت نمایش HTML با نمایش متن و نقش اون در حمله XSS رو با چشم خودمون ببینیم.

<!doctype html>
<html lang="fa" dir="rtl">
<head>
  <meta charset="utf-8">
  <title>Local XSS Learning Lab</title>
</head>
<body>
  <h2>نمایش یک یادداشت آزمایشی</h2>
  <div id="unsafe"></div>
  <div id="safe"></div>
  <script>
    const note = '<strong>سیم گیتارم پاره شده</strong>';
    document.querySelector('#unsafe').innerHTML = note;
    document.querySelector('#safe').textContent = note;
  </script>
</body>
</html>

بعد از باز کردن فایل، دو خروجی می‌بینی. در بخش اول، متن داخل تگ strong پررنگ نمایش داده می‌شه؛ چون innerHTML تگ رو به عنصر HTML تبدیل کرده. در بخش دوم، خودِ علامت‌ها و اسم تگ کنار متن دیده می‌شن؛ چون textContent همون رشته رو نشون داده. تا این مرحله هنوز JavaScript تزریق‌شده‌ای اجرا نکردیم؛ فقط ثابت کردیم این دو مقصد، داده رو یک‌جور تفسیر نمی‌کنن.

یه نکته مهم این وسط هست: هر استفاده‌ای از innerHTML الزاماً باگ نیست. ممکنه برنامه بخواد HTML ثابت و مورداعتماد خودش رو نمایش بده. خطر زمانی جدی می‌شه که ورودیِ قابل‌کنترل کاربر بدون حفاظت مناسب وارد چنین مقصدی بشه. برای یک یادداشت متنی معمولی، هیچ دلیلی نداریم سراغ تفسیر HTML بریم؛ همین انتخاب ساده یکی از بهترین راه‌های جلوگیری از حمله XSS در این سناریوئه.

آزمایش دوم : اجرای کنترل‌شده فقط در فایل محلی

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

const note = `<img src="missing-local-xss-image.png" onerror="console.log('Local XSS demo')">`;

برای دیدن نتیجه، ابزار توسعه‌دهنده مرورگر رو باز کن و بخش Console رو نگاه کن. جایی که رشته به innerHTML داده شده، مرورگر یک عنصر تصویر می‌سازه و چون فایل تصویر وجود نداره، رویداد onerror در محیط محلی فعال می‌شه. اما بخش textContent همون ورودی رو مثل نوشته عادی نشون می‌ده. دقت کن که نتیجه به سیاست امنیتی مرورگر و صفحه هم وابسته‌ست؛ اینجا فقط رفتار پیش‌فرض فایل آزمایش رو بررسی می‌کنیم.

این آزمایش بهت نشون می‌ده چرا حمله XSS فقط «نوشتن یه تگ» نیست. ما باید ببینیم داده از کجا اومده، کد برنامه اون رو کجا مصرف کرده و مرورگر واقعاً چه کاری انجام داده. ضمن اینکه گذاشتن script با innerHTML معمولاً به اجرای مستقیم همون تگ منجر نمی‌شه؛ مثال رویداد تصویر رو عمداً انتخاب کردیم که تفاوت «تفسیر HTML» و «اجرای رفتار» روشن بمونه.

انواع XSS؛ تفاوت Stored، Reflected و DOM-Based

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

Stored XSS چیست؟

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

Reflected XSS چیست؟

در Reflected XSS معمولاً داده از درخواست میاد و بدون اینکه لزوماً ذخیره بشه، در پاسخ همون درخواست نمایش داده می‌شه. مثلاً کاربر عبارتی رو جست‌وجو می‌کنه و سایت اون عبارت رو بالای نتایج تکرار می‌کنه. اگه برنامه این نمایش رو در جای نامناسبی انجام بده، زمینه خطر وجود داره. ولی یه پارامتر URL یا نمایش کلمه جست‌وجو، به‌تنهایی اثبات‌کننده حمله XSS نیست.

DOM-Based XSS چیست؟

در DOM Based XSS، نقطه حساس داخل JavaScript سمت مرورگره. مثلاً اسکریپت صفحه بخشی از URL رو می‌خونه و بدون توجه به اینکه داده قابل‌اعتماده یا نه، مستقیم به innerHTML می‌ده. شاید HTML اولیه‌ای که سرور فرستاده هیچ نشونه مشکوکی نداشته باشه، ولی کد کلاینت خودش مسیر خطرناک رو بسازه. به همین دلیله که فقط دیدن View Source برای تحلیل DOM-Based کافی نیست.

نوعمسیر معمول دادهنقطه‌ای که باید بررسی کنیمبرداشت اشتباه رایج
Storedذخیره و سپس نمایشخروجی پنل‌ها، دیدگاه‌ها و یادداشت‌هاهر متن ذخیره‌شده خطرناک است
Reflectedدرخواست و پاسخصفحه جست‌وجو و پیام‌های خطاهر پارامتر URL آسیب‌پذیر است
DOM-Basedپردازش در مرورگرSource و Sink در کد JavaScriptاگر HTML سرور امن باشد، DOM هم حتماً امن است

پس به‌جای اینکه اسم سه نوع رو حفظ کنی، از خودت بپرس: داده ذخیره شده بود یا مستقیم برگشت؟ تفسیر ناامن روی سرور شکل گرفت یا در مرورگر؟ و در نهایت چه رفتاری واقعاً دیده شد؟ با جواب همین سؤال‌ها می‌تونی خیلی دقیق‌تر از یک برچسب کلی، موضوع رو تحلیل کنی.

Source و Sink؛ مدل پنج‌مرحله‌ای تحلیل حمله XSS

توی تحلیل حمله XSS دو اصطلاح زیاد به کارت میاد: Source و Sink. منبع یا Source جاییه که داده قابل‌کنترل وارد مسیر می‌شه؛ مثلاً فیلد فرم، پارامتر URL یا پاسخ API. مقصد یا Sink هم جاییه که برنامه اون داده رو مصرف می‌کنه؛ مثل textContent یا innerHTML. نکته اینه که مقصدها رفتار یکسانی ندارن. برای همین مهمه مسیر حرکت داده رو از اول تا آخر دنبال کنیم.

User note (Source)
       ↓
Application / API / Storage
       ↓
JavaScript or Template
       ↓
DOM Sink
       ↓
Text display OR executable interpretation

توی مثال آموزشگاه، یادداشت هنرجو منبع داده‌ست و بخش نمایش پیام در پنل مسئول، مقصد اونه. بین این دو هم ممکنه API، دیتابیس یا قالب صفحه قرار گرفته باشه. اگه موقع بررسی کد دنبال همین مسیر بگردی، راحت‌تر می‌فهمی کدوم نقطه باید برای جلوگیری از حمله XSS اصلاح بشه. این مدل پنج‌مرحله‌ای رو برای نظم دادن به تحلیل توضیح می‌دیم، نه به‌عنوان اسم یک استاندارد رسمی تازه.

XSS چه خطرهایی دارد و چه چیزهایی را نباید اغراق کنیم؟

اثر آسیب‌پذیری XSS به شرایط برنامه بستگی داره. در بعضی سناریوها ممکنه محتوای صفحه دستکاری بشه، فرم جعلی دیده بشه یا اسکریپت ناخواسته بتونه در حد دسترسی همون کاربر با برنامه تعامل کنه. اگر قربانی مسئول پنل مدیریتی باشه، پیامد احتمالی می‌تونه جدی‌تر بشه. اما از روی یه پیام Console نمی‌شه نتیجه گرفت کل سرور هک شده یا تمام اطلاعات کاربران لو رفته؛ شدت آسیب باید با شواهد واقعی مشخص بشه.

شاید شنیده باشی که می‌گن «XSS یعنی دزدیدن کوکی». این توضیح خیلی ناقصه. مثلاً ویژگی HttpOnly جلوی خوندن مستقیم کوکیِ مربوط رو با JavaScript می‌گیره، اما اگر اسکریپت ناخواسته داخل صفحه اجرا بشه، ممکنه همچنان بعضی عملیات مجاز همون نشست رو انجام بده. پس هم نباید همه اثرها رو به سرقت کوکی خلاصه کنیم، هم نباید خیال کنیم HttpOnly به‌تنهایی جلوی حمله XSS رو می‌گیره.

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

آموزش XSS

تفاوت XSS و SQL Injection چیست؟

این دو ضعف رو با هم قاطی نکن. در SQL Injection خطر از جایی میاد که ورودی روی ساختار Query دیتابیس اثر می‌ذاره. در حمله XSS مسئله اینه که داده غیرقابل‌اعتماد در زمینه اجرایی مرورگر تفسیر می‌شه. بنابراین هم جای رخ دادن مشکل فرق داره، هم روش دفاع. برای SQL Injection سراغ پرس‌وجوی پارامتری می‌ریم، اما برای XSS باید محل نمایش داده و نحوه Escape یا Sanitization رو درست انتخاب کنیم.

مقایسهXSSSQL Injection
مقصد اثرمرورگر و DOMدیتابیس و Query
ضعف نمونهنمایش ناامن داده در HTMLساخت Query با الحاق رشته
دفاع اصلیEncoding متناسب با خروجی / Safe SinkParameterized Query / Prepared Statement
مثال نابغه‌هایادداشت تعمیر ساز در پنلجست‌وجوی ناامن در اطلاعات بازی‌ها
خطای ذهنی رایجهر HTML ورودی یعنی هکهر فرم جست‌وجو یعنی SQLi

اگه هنوز تفاوت داده و دستور برات جا نیفتاده، اول درس تزریق SQL نابغه‌ها رو مرور کن و بعد برگرد. این دو درس کنار هم نشون می‌دن چرا برنامه‌نویس نباید بذاره داده کاربر تبدیل به دستورِ بخش‌های مختلف سیستم بشه؛ یه بار توی دیتابیس و یه بار توی مرورگر.

جلوگیری از حمله XSS؛ اصل اول : خروجی را متناسب با مقصد امن کن

برای جلوگیری از XSS اول از خودت بپرس داده قراره کجا نمایش داده بشه. اگه فقط یه اسم یا پیام ساده داریم، مقصد متنی انتخاب کن. اگه متن داخل HTML، Attribute یا URL قرار می‌گیره، باید روش Encode یا Escape مخصوص همون محل رو به کار ببری. نمی‌شه یه تابع رو برای همه‌چیز استفاده کرد و مطمئن بود. راهنمای رسمی OWASP برای پیشگیری از XSS دقیقاً روی همین موضوع تأکید می‌کنه.

مثلاً اسم هنرجو داخل متن یه پاراگراف با آدرس لینکی که کاربر وارد کرده یه جور داده نیست. حتی مقدار یه Attribute معمولی هم با متن داخل JavaScript فرق می‌کنه. برای همین به اصطلاح Contextual Output Encoding می‌رسیم؛ یعنی انتخاب دفاع متناسب با مقصد. وقتی این اصل رو بلد باشی، به‌جای حفظ یه فهرست تصادفی از کاراکترهای ممنوع، راه‌حل درست برای جلوگیری از حمله XSS رو پیدا می‌کنی.

جلوگیری از XSS با Safe Sink برای متن‌های ساده

برای یادداشت‌هایی که فقط باید خوانده بشن، استفاده از textContent روی یه عنصر معمولی انتخاب ساده و مناسبیه. نمونه زیر رو برای همین موضوع گذاشتم و این بار عنصر message هم توی خود نمونه وجود داره تا موقع تمرین با مقدار null روبه‌رو نشی. کد، متن حاوی تگ رو نشون می‌ده، بدون اینکه اون رو به عنصر جدید تبدیل کنه.

<div id="message"></div>
<script>
  const note = '<strong>درخواست تعمیر ویولن</strong>';
  const messageBox = document.querySelector('#message');
  messageBox.textContent = note;
</script>

حواست باشه textContent رو هم نباید بدون توجه به نوع عنصر استفاده کنی. این روش برای نشون دادن متن داخل div، span و عناصر معمولی مناسبه؛ اما عنصرهای اجرایی مثل script داستان متفاوتی دارن. وقتی از Safe Sink حرف می‌زنیم، منظورمون استفاده درست در Context مناسبه، نه اینکه اسم یک API رو حفظ کنیم و همه‌جا همون رو به کار ببریم.

جلوگیری از XSS وقتی نمایش HTML واقعاً لازمه

بعضی وقت‌ها واقعاً لازم داریم کاربر بخشی از متنش رو Bold کنه یا لینک مجاز بذاره. اینجا دیگه نمی‌تونیم همه HTML رو به‌صورت متن نمایش بدیم. باید سراغ HTML Sanitization با یه سیاست مشخص بریم؛ یعنی فقط تگ‌ها و ویژگی‌هایی باقی بمونن که برنامه اجازه داده. کتابخانه‌ای مثل DOMPurify برای این کار ساخته شده، ولی باید نسخه به‌روز و روش استفاده درستش رو با مستندات خودش بررسی کنی.

یه دام مهم رو هم بشناس: ممکنه HTML رو درست پاک‌سازی کنی، ولی بعدش دوباره با یه رشته غیرقابل‌اعتماد ترکیبش کنی و دفاع در برابر حمله XSS خراب بشه. ضمن اینکه Sanitization مربوط به HTML قرار نیست خودکار مجوز دسترسی، اعتبار URL یا قواعد آپلود فایل رو درست کنه. هر مشکل رو در جای خودش حل کن تا دفاع تبدیل به یه لایه ظاهری نشه.

XSS در وردپرس؛ چرا esc_html و esc_attr یکی نیستن؟

اگه وردپرس کار می‌کنی، این قسمت رو جدی بگیر. توی قالب و افزونه‌ها مرتب اسم کاربر، عنوان نوشته، مقدار ورودی و آدرس لینک رو چاپ می‌کنیم. وردپرس برای این محل‌ها تابع‌های جداگانه داره؛ مثلاً esc_html() برای متن معمولی داخل HTML، esc_attr() برای Attribute و esc_url() برای آدرس. در آموزش XSS در وردپرس مهمه این تفاوت رو با کد واقعی ببینی، نه فقط با اسم تابع‌ها.

<?php
// این نمونه برای اجرا داخل قالب وردپرسه، نه PHP مستقل.
if ( is_user_logged_in() ) {
    $student_name = wp_get_current_user()->display_name;
    $page_url = get_permalink( get_queried_object_id() );

    if ( is_string( $page_url ) && $page_url !== '' ) {
        ?>
        <p><?php echo esc_html( $student_name ); ?></p>
        <input type="text" value="<?php echo esc_attr( $student_name ); ?>">
        <a href="<?php echo esc_url( $page_url ); ?>">نمایش همین صفحه</a>
        <?php
    }
}

توی نمونه پایین، شناسه کاربر رو از وضعیت ورود وردپرس می‌گیریم و نام همون کاربر رو نمایش می‌دیم؛ بنابراین دیگه به متغیرهای تعریف‌نشده وابسته نیستیم. لینک هم به صفحه فعلی اشاره می‌کنه و برای داده متغیر از Escape مناسب استفاده شده. این فقط نمونه خروجی امنه؛ اجازه دیدن اطلاعات دیگران یا ورود به بخش خصوصی رو باید جدا با منطق Authorization بررسی کنی. مستندات رسمی Escaping در وردپرس هم همین تفکیک رو توضیح می‌ده.

اگر خواستی توی وردپرس یه HTML محدود و مجاز رو از کاربر بپذیری، سراغ سیاستی مثل wp_kses_post() یا تنظیمات محدودتر wp_kses() برو؛ البته فقط وقتی واقعاً HTML لازم داری. برای اسم هنرجو و متن ساده، معمولاً همون Escape در لحظه نمایش کافی و شفاف‌تره. این تمایز بخش مهمی از جلوگیری از حمله XSS در قالب‌های اختصاصی و افزونه‌هاست.

چرا فقط پاک کردن ورودی کافی نیست؟

گاهی می‌بینی کسی پیشنهاد می‌ده موقع ثبت فرم همه علامت‌های < و > رو حذف کنیم و خیال‌مون راحت بشه. این کار هم ممکنه متن سالم رو خراب کنه، هم تمام Contextهای خطرناک رو پوشش نمی‌ده. تازه توی سایت آموزشی، دانشجو حق داره تگ HTML یا تکه کد رو داخل سؤالش بنویسه. پس راه‌حل قرار نیست خراب کردن ورودی باشه؛ باید داده رو موقع مصرف درست مدیریت کنیم.

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

نقش CSP و HttpOnly در جلوگیری از XSS چیست؟

سیاست Content Security Policy یا همون CSP می‌تونه اجرای بعضی اسکریپت‌ها و منابع رو محدود کنه و کمک کنه اثر بعضی حمله‌ها کمتر بشه. ولی تنظیم درستش به ساختار واقعی سایت بستگی داره. یه CSP شلخته یا بیش‌ازحد آزاد شاید حفاظت مورد انتظار رو نده؛ از اون طرف، سیاست خیلی سخت‌گیرانه هم ممکنه قابلیت‌های سالم رو از کار بندازه. پس CSP لایه مکمل جلوگیری از حمله XSS محسوب می‌شه، نه جایگزین اصلاح کد.

ویژگی HttpOnly هم برای کوکی نشست مفیده، چون خوندن مستقیم مقدار اون کوکی رو از طریق JavaScript محدود می‌کنه. Secure درباره ارسال کوکی روی ارتباط امنه و SameSite به ارسال کوکی در موقعیت‌های بین‌سایتی مربوط می‌شه. این تنظیمات مهمن، اما هیچ‌کدوم به‌تنهایی نمی‌گن کد نمایشی ما در برابر حمله XSS امن نوشته شده.

آموزش امنیت وب

آیا WAF می‌تواند حمله XSS را کامل متوقف کند؟

یه WAF می‌تونه درخواست‌هایی با الگوهای مشکوک رو شناسایی یا متوقف کنه، ولی قرار نیست همه خطاهای کدنویسی رو برطرف کنه. مخصوصاً توی بعضی سناریوهای DOM Based XSS پردازش ناامن در مرورگر انجام می‌شه و ممکنه فایروال اصلاً مسیر کامل داده رو نبینه. مثل اسکنرهای دیگه، WAF هم می‌تونه هشدار اشتباه بده یا یه مورد واقعی رو از دست بده.

پس اگه روی سایتت فایروال یا افزونه امنیتی فعاله، خوبه؛ ولی این نباید دلیل بشه متن غیرقابل‌اعتماد رو مستقیم به innerHTML بدی. اول کد رو درست بنویس و بعد از ابزارهای دفاعی برای تکمیل حفاظت کمک بگیر. فرق «لایه دفاعی» با «رفع ریشه آسیب‌پذیری» دقیقاً همینه.

آیا ابزارهای هک می‌توانند همه XSSها را پیدا کنند؟

نه، هیچ ابزاری تضمین نمی‌کنه همه نمونه‌های حمله XSS رو پیدا کنه. بعضی مسیرها فقط بعد از ورود با نقش مشخص، ثبت درخواست و باز شدن یه صفحه خاص فعال می‌شن. شاید اسکنر خودکار اصلاً به اون صفحه نرسه. از اون طرف، دیدن یه هشدار هم لزوماً به معنی اجرای موفق کد نیست. برای تأیید باید مسیر Source تا Sink و رفتار واقعی مرورگر رو بررسی کنیم.

این دقیقاً جاییه که شناخت ابزار از فهم مسئله جدا می‌شه. کسی که Source، Sink و Context رو خوب می‌شناسه می‌تونه انتخاب کنه چه چیزی رو بررسی کنه و کجا نیاز به آزمایش کنترل‌شده هست. اگه می‌خوای بعداً این مهارت رو در مسیر حرفه‌ای دنبال کنی، هاب تست نفوذ و پکیج متخصص تست نفوذ نابغه‌ها رو بر اساس سرفصل‌ها بررسی کن؛ مهم‌تر از نام ابزار، فهم خروجی اونه.

گزارش درست یک یافته XSS چه شکلی است؟

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

بخش گزارشنمونه در سناریوی آموزشگاه موسیقی
موضوعنمایش ناامن یادداشت درخواست تعمیر در پنل آزمایشی
محدودهنسخه تستی و دو حساب ساختگی مجاز
منبع دادهفیلد یادداشت هنرجو
محل نمایشنمای جزئیات درخواست در پنل مسئول
مشاهده قطعیتفسیر HTML از رشته‌ای که باید متن دیده می‌شد
محدودیت مشاهدهاجرای کد در محیط واقعی یا روی حساب دیگر آزمون نشده
ریشه احتمالیاستفاده از HTML Sink بدون کنترل مناسب داده
پیشنهاد اصلاحنمایش متنی یا Sanitization معتبر بر اساس نیاز، همراه با آزمون مجدد

جدول بالا رو عمداً این‌طوری نوشتیم که مشاهده قطعی با حدس قاطی نشه. تفسیر HTML با اجرای JavaScript یکی نیست و اجرای یک پیام محلی هم به معنی دسترسی به اطلاعات حساس نیست. هر نتیجه‌ای باید به همون اندازه‌ای گزارش بشه که آزمایش واقعاً ثابت کرده؛ همین موضوع گزارش رو برای توسعه‌دهنده قابل اتکا می‌کنه.

۵ اشتباه رایج در یادگیری حمله XSS

اشتباه اول : تصور کنیم هر ورودی HTML یعنی آسیب‌پذیری

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

اشتباه دوم : فرض کنیم XSS همیشه به سرور نفوذ می‌کند

اثر رایج XSS در زمینه مرورگر و دسترسی‌های همون برنامه مطرحه، نه اینکه ناگهان کنترل سیستم‌عامل سرور به دست کسی بیفته. برای چنین ادعای بزرگی به شواهد و ضعف‌های اضافی نیاز داریم. توی گزارش امنیتی، از عبارت‌های دقیق‌تر از «کل سایت هک شد» استفاده کن.

اشتباه سوم : استفاده از Escape نامناسب برای همه Contextها

متن داخل پاراگراف، مقدار Attribute، آدرس URL و رشته داخل JavaScript قواعد یکسانی ندارن. اگر یه تابع Escape رو بدون فکر برای همه این قسمت‌ها استفاده کنی، ممکنه هم داده رو خراب کنی و هم حس امنیت کاذب بسازی. همیشه اول محل نمایش رو مشخص کن و بعد روش مناسب همون Context رو برای جلوگیری از حمله XSS انتخاب کن.

اشتباه چهارم : اتکا به WAF یا CSP بدون اصلاح کد

فایروال و CSP می‌تونن به دفاع کمک کنن، ولی اگه کدت داده نامطمئن رو توی یه محل اجرایی تفسیر کنه، مشکل اصلی هنوز سر جاشه. برای جلوگیری از حمله XSS، انتخاب Sink مناسب و Output Encoding درست رو اولویت بده و بعد سراغ کنترل‌های تکمیلی برو.

اشتباه پنجم : تمرین روی سایت واقعی بدون مجوز

برای یاد گرفتن این مفهوم لازم نیست به هیچ وب‌سایت عمومی دست بزنی. فایل HTML محلی و یه محیط تمرینی که مالکشی کاملاً کافیه. حتی اگه یه تکنیک رو بلد باشی، اجرای اون روی سایت دیگران بدون مجوز، تمرین حرفه‌ای حساب نمی‌شه.

قبل از دیدن پاسخ، خودت تحلیل کن

چهار موقعیت زیر رو بخون و قبل از جواب، دو چیز رو مشخص کن: داده از کجا میاد و کجا مصرف می‌شه؟ بعد بپرس چه چیزی واقعاً ثابت شده و چه چیزی هنوز فرضیه‌ست. این تمرین‌ها نیازی به ابزار هجومی ندارن، اما طرز فکر تحلیل حمله XSS رو حسابی تقویت می‌کنن.

تمرین اول : یک پیام فقط باید نمایش داده شود

فرض کن برنامه‌نویس اسم ساز رو از فرم می‌گیره و با innerHTML داخل یه کارت نشون می‌ده. چه تغییری پیشنهاد می‌دی؟ پاسخ: چون فقط اسم ساز رو می‌خوایم نمایش بدیم، textContent گزینه مناسب‌تریه. برای اعلام آسیب‌پذیری واقعی هم باید ببینیم ورودی چطور به این خط کد می‌رسه.

تمرین دوم : داده از URL وارد می‌شود

یه صفحه بخشی از URL رو با JavaScript می‌خونه و به innerHTML می‌ده. آیا چون سرور در HTML اولیه کد مشکوکی نفرستاده، کار تمومه؟ پاسخ: نه. کد سمت مرورگر می‌تونه مستقل از HTML اولیه یه مسیر DOM-Based XSS بسازه. باید Source و Sink واقعی بررسی بشن.

تمرین سوم : کوکی HttpOnly است

کوکی نشست یه سایت HttpOnly داره. آیا دیگه خیالمون از XSS راحته؟ پاسخ: نه. خوندن مستقیم اون کوکی سخت‌تر می‌شه، اما اجرای کد ناخواسته در صفحه ممکنه آثار دیگه‌ای داشته باشه. HttpOnly مکمل امنیته، نه راه‌حل تمام XSSها.

تمرین چهارم : یک افزونه امنیتی فعال است

یه سایت وردپرسی WAF فعالی داره، ولی قالب اختصاصی اسم کاربر رو بدون Escape چاپ می‌کنه. آیا افزونه امنیتی کافیه؟ پاسخ: نه. برای متن HTML باید در لحظه خروجی از تابع مناسب مثل esc_html() استفاده بشه و مجوز دسترسی هم جدا بررسی بشه. افزونه امنیتی جای کدنویسی درست رو نمی‌گیره.

چطور برای یادگیری حرفه‌ای XSS آماده شویم؟

اگه تازه وارد آموزش امنیت وب شدی، پیشنهاد می‌کنم اول HTML، DOM و JavaScript پایه رو یاد بگیری. بعد با درخواست و پاسخ HTTP، Session و تفاوت داده کاربر با کد برنامه آشنا شو. وقتی این‌ها جا بیفتن، آموزش XSS و تحلیل حمله XSS دیگه یه مشت رشته عجیب نیست؛ می‌تونی بفهمی هر ورودی چرا در یه Context خاص خطرناک یا بی‌خطره.

قدم بعدی اینه که توی آزمایشگاه شخصی مسیر Source تا Sink رو دنبال کنی، خروجی مرورگر رو ببینی و کنترل‌هایی مثل CSP رو در حد نیاز بشناسی. اگه برنامه‌نویس وردپرسی، روی Escape خروجی در قالب تمرین کن؛ اگر به ارزیابی امنیت علاقه داری، از مسیر آموزش هک سایت و امنیت وب ادامه بده. توی هر دو مسیر، داشتن مجوز آزمایش به اندازه بلد بودن روش مهمه.

هک سایت

منابع رسمی برای آموزش XSS و بررسی دقیق‌تر

برای اینکه اطلاعاتت به‌روز و قابل استناد باشه، راهنمای جلوگیری از XSS در OWASP و راهنمای DOM-Based XSS رو ببین. برای تفاوت رفتار innerHTML و textContent هم مستندات MDN مناسبه. اگر کد وردپرس می‌نویسی، راهنمای رسمی Escaping وردپرس رو کنار دستت داشته باش. این‌ها مرجع بررسی ادعاهای فنی مقاله‌ان؛ مثال آموزشگاه و تمرین‌ها برای همین درس طراحی شدن.

سؤالات متداول درباره حمله XSS

حمله XSS چیست به زبان ساده؟

حمله XSS وقتی ممکن می‌شه که داده غیرقابل‌اعتماد از یه مسیر ناامن به محل تفسیر اجرایی صفحه برسه. مهم اینه که مرورگر چه رفتاری با اون داده انجام می‌ده، نه اینکه کاربر فقط یه تگ یا کلمه خاص نوشته باشه.

آیا XSS همان هک سایت است؟

نه. XSS یکی از خانواده‌های آسیب‌پذیری امنیت وبه. هک سایت می‌تونه علت‌های دیگه‌ای مثل ضعف کنترل دسترسی یا SQL Injection هم داشته باشه. شدت هر مورد رو باید با شواهد و شرایط واقعی سنجید.

فرق Stored XSS و Reflected XSS چیست؟

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

DOM-Based XSS چه فرقی با بقیه دارد؟

این اصطلاح بیشتر درباره ضعفِ پردازش داده در JavaScript و DOM مرورگره. Stored و Reflected مسیر ورود داده رو توصیف می‌کنن، بنابراین ممکنه با سناریوی DOM-Based هم‌پوشانی داشته باشن.

آیا استفاده از textContent همیشه امن است؟

برای نمایش متن توی یه عنصر معمولی، textContent انتخاب خوبیه. اما روی عنصر اجرایی مثل script نباید بدون بررسی از همین قاعده نتیجه امنیتی بگیری. همیشه Context رو در نظر بگیر.

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

نه. CSP و HttpOnly می‌تونن بعضی خطرها رو محدود کنن، ولی اگه کد برنامه داده نامطمئن رو به‌صورت اجرایی تفسیر کنه، هنوز باید همون مشکل رو در کد اصلاح کنی.

برای جلوگیری از XSS در وردپرس چه کنیم؟

برای متن ساده از esc_html()، برای Attribute معمولی از esc_attr() و برای آدرس لینک از esc_url() استفاده کن. اگه نمایش HTML محدود لازم داری، سیاست مناسب با wp_kses() یا wp_kses_post() رو بررسی کن.

برای یادگیری XSS باید Kali Linux نصب کنیم؟

نه. برای فهم و تمرین اولیه حمله XSS، یه مرورگر، فایل HTML محلی و آشنایی با JavaScript و DOM کفایت می‌کنه. ابزارهای تخصصی رو بعداً در محیط مجاز و متناسب با نیاز یاد بگیر.

جمع‌بندی : امنیت وب با فهمیدن مرز داده و کد شروع می‌شود

حالا می‌دونی حمله XSS از کجا شروع می‌شه: داده‌ای که قرار بوده فقط متن باشه، در نقطه‌ای از مسیر به شکل ناامن تفسیر می‌شه. با مثال آموزشگاه موسیقی دیدیم چرا innerHTML با textContent فرق داره، سه اسم رایج Stored و Reflected و DOM-Based دقیقاً چی رو توصیف می‌کنن و چرا جلوگیری از XSS به Context و نحوه مصرف داده وابسته‌ست. همین فهم از حفظ کردن چند Payload خیلی ارزشمندتره.

اگه بخوای این مسیر رو ادامه بدی، از هاب امنیت نابغه‌ها وارد درس‌های امنیت وب شو و موضوع‌ها رو مرحله‌به‌مرحله جلو ببر. اگه هم به تمرین‌های ساختارمند و تست نفوذ قانونی علاقه داری، پکیج متخصص تست نفوذ رو از نظر سرفصل‌ها و سطح آموزش بررسی کن. چیزی که از این درس باید برات بمونه اینه: برای پیدا کردن و اصلاح یه ضعف، اول مسیر داده رو بفهم، بعد درباره خطرش با مدرک حرف بزن.

می‌خواهی عمیق‌تر جلو بروی؟

این درس فقط یک بخش از مسیر کامل است

اگر می‌خواهی مباحث را به‌ترتیب، همراه تمرین و پروژه دنبال کنی، مسیر کامل دوره را ببین.

مشاهده پکیج تست نفوذ