آسیبپذیری Cross-Site Scripting (XSS) یکی از باگهای مهم امنیت برنامههای وب است. در این آموزش یاد میگیری چگونه XSS را در یک محیط آزمایشگاهی شناسایی کنی، نوع آسیبپذیری را تشخیص بدهی، نتیجه را تحلیل کنی و در نهایت راهکار اصلاح آن را اجرا کنی.
این آموزش روی آزمایشگاه شخصی و برنامههای عمداً آسیبپذیر متمرکز است؛ نه نفوذ به سایتهای واقعی بدون مجوز.
۱. آسیبپذیری XSS چیست؟
XSS زمانی رخ میدهد که برنامه وب دادهای را که کاربر کنترل میکند، بهصورت ناامن در صفحه نمایش دهد و مرورگر آن را بهعنوان کد اجرایی تفسیر کند.
برای مثال، تصور کن یک سایت بخشی برای ثبت نظر دارد. کاربر متنی وارد میکند و سایت همان متن را بدون پردازش امن در صفحه قرار میدهد. اگر این ورودی بتواند بهجای متن عادی، کد JavaScript اجرا کند، احتمال وجود XSS مطرح میشود.
سه نوع اصلی XSS
Reflected XSS — بازتابی
ورودی مخرب از درخواست کاربر دریافت و در پاسخ همان درخواست، بهصورت ناامن نمایش داده میشود؛ مثلاً در صفحه نتایج جستوجو.
Stored XSS — ذخیرهشده
ورودی ناامن در پایگاه داده یا محل ذخیره دیگری باقی میماند و هنگام مشاهده صفحه توسط کاربران، اجرا میشود.
DOM-based XSS
کد سمت مرورگر دادهای کنترلشده توسط کاربر را به شکلی ناامن وارد DOM میکند؛ حتی ممکن است پاسخ سرور بهخودیخود حاوی کد آسیبپذیر نباشد.
۲. ساخت محیط آزمایشگاهی امن
برای تمرین از Kali Linux یا هر سیستم دیگری استفاده کن. نیازی به آزمایش روی سایت عمومی نیست.
پیشنهاد برای آزمایشگاه:
-
مرورگر: Firefox یا Chromium
-
ابزار بررسی درخواستها: Burp Suite Community
-
محیط آموزشی: OWASP Juice Shop یا PortSwigger Web Security Academy
-
سطح تمرین: فقط آزمایشگاه محلی یا محیطی که مجوز صریح تست آن را داری
برای شروع میتوانی از این منابع رسمی استفاده کنی:
۳. آزمایش عملی اول: تشخیص Reflected XSS
در این سناریو یک صفحه محلی میسازیم که ورودی جستوجو را بهشکل ناامن وارد HTML میکند. سپس با یک آزمایش بیخطر بررسی میکنیم که مرورگر ورودی را متن میبیند یا کد اجرایی.
مرحله اول: ساخت فایل آزمایشگاهی
فایلی به نام xss-lab.html بساز و کد زیر را داخل آن قرار بده:
<!doctype html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>آزمایشگاه محلی XSS</title>
</head>
<body>
<h1>آزمایشگاه XSS</h1>
<form id="search-form">
<label for="q">عبارت آزمایشی:</label>
<input id="q" name="q">
<button type="submit">جستوجو</button>
</form>
<h2>نتیجه:</h2>
<div id="result"></div>
<script>
const form = document.getElementById("search-form");
const input = document.getElementById("q");
const result = document.getElementById("result");
form.addEventListener("submit", function (event) {
event.preventDefault();
// عمداً آسیبپذیر؛ فقط برای آزمایش محلی
result.innerHTML = input.value;
});
</script>
</body>
</html>
فایل را در مرورگر باز کن. این نمونه یک آزمایشگاه مستقل است و هیچ درخواست یا حملهای به سایت دیگری ارسال نمیکند.
مرحله دوم: آزمایش ورودی عادی
در کادر عبارت زیر را وارد کن:
سلام، این یک آزمایش است.
باید متن در بخش نتیجه نمایش داده شود.
مرحله سوم: بررسی امکان اجرای کد
حالا این ورودی آزمایشی را وارد کن:
<img src=x onerror="alert('XSS Lab')">
در این محیط محلی، اگر مرورگر رویداد error تصویر را اجرا کند، یک پنجره با عنوان XSS Lab نمایش داده میشود.
نتیجه آزمایش: ورودی کاربر بهعنوان HTML تفسیر شده و ویژگی رویداد آن امکان اجرای JavaScript را ایجاد کرده است. این رفتار، در کد آزمایشگاهی ما، نمونهای از DOM-based XSS است؛ نه Reflected XSS واقعی در یک برنامه مبتنی بر درخواست و پاسخ سرور.
نکته مهم: این تست فقط یک اثبات مفهوم (PoC) بیخطر است و بهتنهایی وجود آسیبپذیری در هیچ سایت دیگری را ثابت نمیکند.
۴. آزمایش عملی دوم: اصلاح آسیبپذیری
حالا باید همان برنامه را ایمن کنیم.
در کد قبلی این خط آسیبپذیر بود:
result.innerHTML = input.value;
آن را با این خط جایگزین کن:
result.textContent = input.value;
دوباره ورودی آزمایشی مرحله قبل را وارد کن. این بار باید کد بهصورت متن نمایش داده شود و پنجره هشدار اجرا نشود.
چرا این اصلاح کار میکند؟
-
innerHTMLرشته را بهعنوان HTML تجزیه میکند. -
textContentداده را بهعنوان متن قرار میدهد و تگهای واردشده را اجرا نمیکند.
برای نمایش متن ساده، textContent معمولاً انتخاب مناسبتری است. اگر واقعاً نیاز به پذیرش HTML داشته باشی، باید از یک پاکساز HTML معتبر و پیکربندیشده استفاده کنی؛ حذف چند رشته یا کاراکتر با فیلتر ساده، دفاع قابل اتکایی نیست.
۵. آزمایش عملی سوم: شناسایی Stored XSS
در Stored XSS ورودی ابتدا ذخیره میشود و بعداً در صفحهای دیگر به کاربران نمایش داده میشود. برای مثال، بخش دیدگاههای یک سایت ممکن است متن نظر را در پایگاه داده ذخیره کند و هنگام بازکردن صفحه، آن را بهصورت ناامن نمایش دهد.
سناریوی آزمایشگاهی
۱. کاربر ورودی آزمایشی ثبت میکند
۲. برنامه ورودی را ذخیره میکند
۳. مرورگر صفحه را باز میکند
۴. در صورت رندر ناامن، امکان اجرای کد وجود دارد
برای بررسی این نوع باگ:
-
در آزمایشگاه مجاز، بخشی مانند دیدگاهها یا پروفایل را پیدا کن.
-
یک ورودی متنی مشخص و بیخطر ثبت کن.
-
صفحه را مجدداً بارگذاری کن و بررسی کن که آیا ورودی همچنان وجود دارد.
-
در صورت مجاز بودن تست فعال، از یک PoC بیخطر برای بررسی اجرای کد استفاده کن.
-
همان صفحه را با حساب آزمایشی دوم باز کن تا مشخص شود رفتار به کاربر دیگری نیز منتقل میشود یا خیر.
-
پس از ثبت نتیجه، داده آزمایشی را حذف کن.
اگر ورودی ذخیره شود ولی بهصورت متن امن نمایش داده شود، صرف ذخیرهشدن آن به معنای وجود XSS نیست.
۶. آزمایش عملی چهارم: بررسی با Burp Suite
ابزار Burp Suite به تو اجازه میدهد درخواستها و پاسخهای HTTP را بررسی کنی.
مراحل کار در آزمایشگاه:
-
برنامه آزمایشگاهی را اجرا و مرورگر را به Proxy محلی Burp متصل کن.
-
بخش جستوجو، دیدگاه یا فرم آزمایشی را باز کن.
-
یک ورودی عادی ارسال کن و درخواست مربوطه را در بخش HTTP history پیدا کن.
-
بررسی کن ورودی در کدام بخش درخواست قرار گرفته است؛ برای مثال، پارامتر URL، بدنه درخواست یا کوکی.
-
پاسخ سرور را بررسی کن: آیا ورودی بازتاب یافته؟ آیا کاراکترهای ویژه کدگذاری شدهاند؟
-
در صورت نیاز و فقط در محدوده مجاز، درخواست را در Repeater بازبینی و تغییرات کنترلشده اعمال کن.
-
در صورت مشاهده رفتار مشکوک، اجرای واقعی را در مرورگر آزمایشگاهی تأیید کن.
نکته تخصصی: مشاهده رشته ورودی در پاسخ HTTP بهتنهایی برای اثبات XSS کافی نیست. باید مشخص شود داده در چه زمینهای قرار گرفته است؛ متن HTML، ویژگی HTML، رشته JavaScript یا URL هرکدام قواعد ایمنسازی متفاوتی دارند.
۷. روشهای مهم شناسایی XSS در برنامههای وب
| محل ورودی | چه چیزی را بررسی کنیم؟ | نکته امنیتی |
|---|---|---|
| جستوجو | بازتاب عبارت در صفحه | زمینه نمایش داده را بررسی کن |
| دیدگاهها | ذخیره و نمایش مجدد ورودی | رندر ناامن میتواند Stored XSS ایجاد کند |
| پروفایل کاربر | نام، توضیحات و فیلدهای قابل ویرایش | خروجی باید در محل استفاده ایمن شود |
| پارامترهای URL | استفاده از location.search یا location.hash |
کد سمت مرورگر را بررسی کن |
| قالبها و ویجتها | درج محتوای پویا در HTML | از تزریق مستقیم HTML اجتناب کن |
۸. راهکارهای دفاعی برای توسعهدهندگان
۱. کدگذاری خروجی متناسب با زمینه
برای نمایش متن در HTML، کاراکترهای ویژه باید به شکل مناسب کدگذاری شوند. کدگذاری متن HTML بهتنهایی برای زمینه JavaScript یا URL کافی نیست.
۲. استفاده از APIهای امن DOM
برای متن ساده از textContent بهجای innerHTML استفاده کن و از ساخت رشتههای HTML با ورودی غیرقابل اعتماد بپرهیز.
۳. پاکسازی HTML در صورت نیاز واقعی
اگر محصول باید HTML کاربر را بپذیرد، از کتابخانه معتبر مانند DOMPurify استفاده کن و سیاست مجاز محتوا را محدود نگه دار.
۴. پیادهسازی Content Security Policy
یک CSP مناسب میتواند اجرای برخی اسکریپتهای تزریقشده را محدود کند؛ اما جایگزین اصلاح کد آسیبپذیر نیست.
۵. حذف الگوهای ناامن
از رویدادهای درونخطی مانند onclick و ساخت پویای کد JavaScript با دادههای کاربر دوری کن. داده را در قالبی نگه دار که بهعنوان کد تفسیر نشود.
۹. چکلیست نهایی ارزیابی
چکلیست تست نفوذ
0 از 7
کپی چکلیست
۱۰. قالب گزارش آسیبپذیری XSS
برای هر مورد تأییدشده در گزارش تست نفوذ، این اطلاعات را ثبت کن:
-
عنوان: نوع XSS شناساییشده
-
محل آسیبپذیر: صفحه، پارامتر یا مؤلفه
-
پیششرط: سطح دسترسی لازم برای بازتولید
-
مراحل بازتولید: مراحل دقیق در محیط مجاز
-
شواهد: درخواست، پاسخ و تصویر نتیجه آزمایش
-
اثر امنیتی: بر اساس دسترسی واقعی و شرایط برنامه
-
راهکار اصلاح: تغییر کد، کدگذاری خروجی یا پاکسازی معتبر
-
نتیجه تست مجدد: تأیید اصلاح یا نیاز به بررسی بیشتر
منبع تکمیلی برای استانداردهای تست: OWASP Web Security Testing Guide.
جمعبندی برای صفر و یک سایبر: هدف تست نفوذ فقط اجرای یک Payload نیست؛ باید مسیر ورود داده، نحوه تفسیر آن در مرورگر، اثر امنیتی واقعی و اصلاح قطعی را هم بررسی کنی.

دیدگاه شما