Self-XSS + CSRF منجر به سرقت حساب کاربری
یک Self-XSS و یک CSRF که بهتنهایی Impact محدودی داشتند، اما کنار هم یک زنجیره کامل برای تصاحب حساب ساختند.
خلاصه
در جریان بررسی قابلیت پروفایل یک سرویس، ابتدا با یک Self-XSS در پارامتر ایمیل مواجه شدم. در ادامه، هنگام بررسی همان بخش، یک CSRF در فرآیند تغییر ایمیل پیدا کردم.
هر دو Finding بهتنهایی محدود بودند؛ اما وقتی رفتارشان را کنار هم قرار دادم، متوجه شدم میتوان از CSRF برای قرار دادن Payload در حساب قربانی و از XSS برای اجرای آن استفاده کرد.
نتیجه:
دو آسیبپذیری با Impact محدود، در کنار هم به Account Takeover منجر شدند.
🔎 همهچیز از یک فیلد ایمیل شروع شد
در جریان بررسی بخش Profile، یکی از پارامترهایی که توجهم را جلب کرد، فیلد ایمیل بود.
رفتار برنامه این بود که مقدار ایمیل را در صفحه پروفایل کاربر نمایش میداد.
در نگاه اول، این رفتار کاملاً عادی بود.
اما در تستهای ورودی متوجه شدم مقدار واردشده به شکل مناسبی در Context مربوط به HTML مدیریت نمیشود.
همینجا اولین سرنخ ظاهر شد.
🧪 Self-XSS
با قرار دادن یک ورودی آزمایشی HTML، متوجه شدم امکان تزریق Attribute در خروجی وجود دارد.
پیلود استفاده شده:
"+onfocus=alert(origin)+autofocus
خروجی:
<input type=”text” id=”email” name=”email” value=" onfocus=alert(origin) autofocus>
اما نتیجه تست این بود که مقدار کنترلشده توسط کاربر در HTML صفحه Profile قرار میگرفت و در شرایط مشخصی میتوانست باعث اجرای JavaScript شود.
در نتیجه:
User Input
│
▼
Email Parameter
│
▼
Profile Page
│
▼
Unsafe HTML Context
│
▼
JavaScript Execution
اما یک مشکل وجود داشت.
این آسیبپذیری در حالت عادی Self-XSS محسوب میشد.
یعنی مهاجم نمیتوانست صرفاً با ارسال یک لینک، Payload را روی حساب قربانی قرار دهد.
کاربر باید ابتدا مقدار مخرب را در حساب خودش وارد میکرد.
بنابراین، برای Account Takeover هنوز یک قطعه از پازل کم بود.
🧩 دنبال کردن سرنخ دوم
بهجای اینکه همانجا متوقف شوم، بررسی کردم ببینم تغییر ایمیل در Backend چگونه انجام میشود.
در جریان این بررسی، یک Endpoint پیدا کردم که برای تغییر یا تأیید ایمیل مورد استفاده قرار میگرفت.
رفتار آن چند نکته قابل توجه داشت:
- از متد GET استفاده میکرد؛
- CSRF Token نداشت؛
- برای انجام عملیات به Re-authentication نیاز نداشت.
بهصورت ساده:
GET /profile/...?...
این یعنی اگر کاربر در Session معتبر قرار داشت، امکان ارسال درخواست تغییر ایمیل با همان سطح دسترسی وجود داشت.
⚠️ اما CSRF هم بهتنهایی کافی نبود
در نگاه اول ممکن بود تصور شود:
«پس مهاجم میتواند ایمیل قربانی را تغییر دهد و حساب را بگیرد.»
اما بررسی دقیقتر نشان داد چنین چیزی اتفاق نمیافتد.
فیلد ایمیل در این سرویس مستقیماً در فرآیند Authentication استفاده نمیشد و تغییر آن بهتنهایی باعث از دست رفتن حساب نمیشد.
پس وضعیت فعلی این بود:
Self-XSS
↓
Impact محدود
CSRF
↓
Impact محدود
دو Finding داشتم، اما هنوز ATO نداشتم.
اینجا بود که تصمیم گرفتم بهجای بررسی جداگانه، ارتباط بین این دو رفتار را بررسی کنم.
🔗 اتصال دو قطعه پازل
سؤال اصلی این بود:
اگر بتوانم با CSRF مقدار ایمیل را تغییر دهم، آیا میتوانم همان مقدار مخرب مربوط به XSS را بدون تعامل مستقیم قربانی وارد حساب او کنم؟
اگر جواب مثبت بود، Self-XSS دیگر Self باقی نمیماند.
و دقیقاً همین اتفاق افتاد.
💥 ساخت زنجیره حمله
سناریوی نهایی تقریباً به این شکل بود:
Attacker
│
▼
Malicious Request
│
▼
CSRF
│
▼
Victim's Email Field
│
▼
XSS Payload Stored
│
▼
Victim Opens Profile
│
▼
JavaScript Executes
│
▼
Session Information
│
▼
Account Takeover
در واقع CSRF کاری را انجام میداد که قربانی نباید بدون اطلاع خودش انجام دهد:
قرار دادن مقدار تحت کنترل مهاجم در فیلد ایمیل.
و Self-XSS نیز همان مقدار را هنگام نمایش Profile به Code تبدیل میکرد.
🎯 از Self-XSS تا Stored XSS
این دقیقاً نقطهای بود که ماهیت Finding تغییر کرد.
قبل از Chain:
Attacker → خودش Payload را وارد میکند
→ خودش XSS را میبیند
اما بعد از Chain:
Attacker
│
▼
CSRF
│
▼
Victim's Account
│
▼
Malicious Value Stored
│
▼
Victim's Profile
│
▼
JavaScript Execution
یعنی CSRF عملاً محدودیت اصلی Self-XSS را دور میزد.
دیگر قربانی لازم نبود خودش Payload را وارد کند.
🔐 رسیدن به Account Takeover
در مرحله نهایی، با اجرای JavaScript در Context حساب قربانی، امکان دسترسی به اطلاعات Session مورد نیاز برای ادامه سناریو بررسی شد.
برای حفظ امنیت و جلوگیری از انتشار جزئیات قابل سوءاستفاده، بخش مربوط به استخراج و انتقال اطلاعات حساس در این Write-up بازسازی نشده است.
اما در محیط تست و با حسابهای تحت کنترل خودم، Chain بهصورت کامل تأیید شد و امکان استفاده از Session قربانی برای دسترسی به حساب او اثبات شد.
بنابراین نتیجه نهایی:
Self-XSS + CSRF = Account Takeover
🧠 چیزی که این Finding را جالب کرد
اگر هر دو آسیبپذیری را جداگانه بررسی میکردم، احتمالاً Impact نهایی دیده نمیشد.
گزارش اول:
Self-XSS
و گزارش دوم:
CSRF on Email Update
هرکدام بهتنهایی Impact محدودی داشتند.
اما Bug Bounty همیشه درباره پیدا کردن آسیبپذیریهای مستقل نیست.
گاهی مهمترین سؤال این است:
«آیا میتوانم دو رفتار ظاهراً مستقل را به یکدیگر متصل کنم؟»
در این مورد، پاسخ مثبت بود.
CSRF مسیر ورود Payload را فراهم کرد و XSS نیز مسیر اجرای آن را.
🛡️ راهکار پیشنهادی
برای جلوگیری از این زنجیره، هر دو بخش باید بهصورت مستقل اصلاح شوند.
جلوگیری از XSS
- Encode مناسب خروجی بر اساس Context
- Sanitization صحیح ورودی در صورت نیاز
- استفاده از Frameworkهای دارای Auto-Escaping
- جلوگیری از قرار دادن داده کاربر در Contextهای ناامن HTML
جلوگیری از CSRF
- استفاده از CSRF Token برای عملیات State-Changing
- عدم استفاده از GET برای تغییر اطلاعات
- استفاده صحیح از
SameSiteCookie - در نظر گرفتن Re-authentication برای تغییرات حساس حساب
مهمتر از همه
حتی اگر هر آسیبپذیری بهتنهایی Impact محدودی داشته باشد، باید بررسی شود که آیا ترکیب چند ضعف امنیتی میتواند Impact بسیار بزرگتری ایجاد کند یا خیر.
📌 جمعبندی
این بررسی با یک فیلد ساده ایمیل شروع شد.
ابتدا یک Self-XSS پیدا شد.
بعد یک CSRF.
در نگاه اول، هیچکدام بهتنهایی Account Takeover ایجاد نمیکردند.
اما وقتی مسیر داده را دنبال کردم، مشخص شد:
CSRF میتواند مقدار مخرب را وارد حساب قربانی کند.
و سپس:
XSS میتواند همان مقدار را در Context قربانی اجرا کند.
در نهایت، این دو آسیبپذیری در کنار یکدیگر به یک زنجیره کامل منجر شدند:
Self-XSS
+
CSRF
↓
Account Takeover
این رایتآپ را خواندید؟ اگر روی پروژه یا پژوهش مشابهی کار میکنید، برای همکاری در تماس باشید.