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

حمله CSRF چیست؟ یک تعریف ساده که واقعاً به درد میخورد
حمله CSRF مخفف Cross-Site Request Forgery یا همون «جعل درخواست بینسایتی»ه. ماجرا معمولاً اینطوریه که مرورگرِ یک کاربر واردشده، تحت شرایط خاص از طرف یک صفحه دیگه درخواستی به سایت اصلی میفرسته و سایت هم اون رو انجام میده؛ درحالیکه کاربر چنین کاری رو نخواسته. مهاجم برای شکل گرفتن این وضعیت لزوماً نیازی به دونستن رمز، خوندن کوکی یا دیدن پاسخ سرور نداره.
برای فهم حمله CSRF بدون قاطی کردن مفاهیم، دو تا سؤال رو از هم جدا کن: «این درخواست به حساب چه کسی وصله؟» و «آیا اون شخص خودش خواسته این کار انجام بشه؟». احراز هویت بیشتر جواب سؤال اوله؛ سازوکار ضد CSRF کمک میکنه سرور درباره سؤال دوم هم شواهد قابل اتکا داشته باشه. ضمن اینکه اجازه انجام همون عملیات روی همون داده، یک بررسی جداگانه به اسم Authorization میخواد.
معمولاً پای حمله CSRF وقتی جدی وسط میاد که درخواست چیزی رو تغییر بده؛ مثلاً ایمیل بازیابی، نشانی تحویل یا مشخصات تسویه حساب. ولی حواست باشه هر فرمی که توکن نداره الزاماً آسیبپذیر نیست. باید ببینیم عملیات چیه، احراز هویت چطور انجام میشه، مرورگر چه کوکیهایی میفرسته و سرور چه کنترلهای دیگهای داره.
داستان اختصاصی : بازارچه «هزاررنگ» و یک تغییر که صاحب غرفه نخواسته بود
برای درک حمله CSRF بیا یک مثال قابل لمس بزنیم. یک بازارچه ساختگی به اسم «هزاررنگ» داریم که هنرمندها توش غرفه میسازن و محصولات دستساز میفروشن. «نسترن» فروشنده دفترهای دستسازه و «کاوه» هم توی پشتیبانی کار میکنه. این اسمها و اتفاقها واقعی نیستن؛ فقط کمک میکنن جریان درخواست رو بدون درگیر شدن با اطلاعات خصوصی کسی دنبال کنیم.
نسترن صبح وارد پنل غرفهاش شده و مرورگرش نشست معتبری داره. عصر، درحالیکه هنوز از حساب خارج نشده، یک صفحه بیربط رو توی تب دیگه باز میکنه. حالا فرض کن اون صفحه بتونه درخواستی برای تغییر یک تنظیم ایجاد کنه، مرورگر هم طبق قوانین کوکی نشست رو همراهش بفرسته و بازارچه هم درخواست رو بدون کنترل ضد CSRF بپذیره. نتیجه ممکنه تغییر تنظیمات نسترن باشه، با اینکه خودش از داخل پنل هیچ تغییری ثبت نکرده.
البته صرف باز کردن یک صفحه ناشناس مساوی هک شدن نیست. مرورگرهای امروزی، ویژگی SameSite و دفاعهای سمت سرور توی خیلی از شرایط جلوی این اتفاق رو میگیرن. ما عمداً داریم یک سناریوی مشروط رو بررسی میکنیم؛ یعنی باید همه پیششرطها درست کنار هم قرار بگیرن تا حمله CSRF واقعاً قابل انجام باشه.
برای تحلیل حمله CSRF، بهجای حفظ کردن اسمش، دنبال ردّ درخواست بریم: از کدوم صفحه شروع شده؟ کوکی همراهش بوده؟ سرور توکن یا مبدأ رو بررسی کرده؟ اصلاً تغییر واقعاً ثبت شده؟ این سؤالها کمک میکنن فرق یک هشدار ساده با یک آسیبپذیری اثباتشده رو بفهمیم.
تحلیل حمله CSRF با مدل «سه امضا»؛ هویت، قصد، اختیار
من برای تحلیل درخواستهای حساس، از مدل ساده «سه امضا» استفاده میکنم: هویت یعنی چه کسی وارد شده، قصد یعنی درخواست از جریان مورد اعتماد شروع شده یا نه، و اختیار یعنی همون کاربر اجازه انجام این کار روی همین داده رو داره یا نه. این اسم فقط یک مدل آموزشی برای توضیح بهتره، نه یک استاندارد رسمی جدید.
| امضا | پرسش سرور | نمونه دفاع |
|---|---|---|
| هویت (Authentication) | چه کاربری پشت این نشست است؟ | مدیریت نشست و احراز هویت معتبر |
| قصد (Request Intent) | درخواست حساس از جریان قابل اعتماد آمده؟ | توکن ضد CSRF و کنترل مبدأ |
| اختیار (Authorization) | این کاربر حق تغییر همین مورد را دارد؟ | سیاست مجوز سمت سرور و کنترل سطح رکورد |
مثلاً نسترن حق تغییر تنظیمات غرفه خودش رو داره و با نشست معتبر وارد شده؛ پس هویت و اختیار ممکنه درست باشن. اما هنوز مشخص نیست درخواست تغییر واقعاً خواسته خودش بوده یا یک صفحه دیگه باعث ارسالش شده. توکن ضد CSRF به بررسی این بخش کمک میکنه؛ از اون طرف، داشتن توکن هم به نسترن اجازه تغییر غرفه بقیه رو نمیده.
حمله CSRF چطور کار میکند؟ جریان واقعی از نگاه مرورگر
مرحله اول : سایت اصلی نشست معتبر دارد
توی حمله CSRF، کوکی نشست نقش مهمی داره؛ وقتی وارد پنل میشی، سرور معمولاً با همون کوکی تو رو میشناسه. مرورگر هم بر اساس دامنه، مسیر، HTTPS و تنظیم SameSite تصمیم میگیره کوکی رو با هر درخواست بفرسته یا نه. نکته مهم اینه که ضمیمه شدن کوکی میتونه کاملاً خودکار باشه؛ مرورگر برای تکتک درخواستها از کاربر اجازه نمیگیره.
مرحله دوم : یک درخواست از زمینهای دیگر آغاز میشود
حالا ممکنه یک صفحه دیگه باعث ناوبری یا ارسال فرم به سایت اصلی بشه. اینکه مرورگر بتونه چنین درخواستی بفرسته، با اینکه JavaScript اون صفحه بتونه پاسخ رو بخونه فرق داره. این دو موضوع رو یکی نکن؛ محدودیت خواندن پاسخ بهتنهایی ثابت نمیکنه هیچ عملیات حساس سمت سرور اتفاق نیفتاده.
مرحله سوم : مقصد درخواست را پردازش میکند
در این بخش از حمله CSRF، نوبت تصمیم سروره. اگر فقط بگه «کوکی معتبره، پس تغییر رو انجام بده»، در یک پیکربندی آسیبپذیر ممکنه درخواست جعلی رو قبول کنه. ولی وقتی توکن معتبر، بررسی مبدأ یا یک دفاع متناسب دیگه رو مطالبه میکنه، درخواست فاقد شواهد لازم رد میشه. اصل ماجرا همین بررسی مستقله.
مرحله چهارم : اثر واقعی را باید دید
برای اثبات حمله CSRF هم صرف دیدن کد HTTP کافی نیست. پاسخ ۲۰۰ ممکنه یک پیام خطا باشه و پاسخ ۴۰۳ ممکنه نشون بده کنترل درست کار کرده. توی محیط آزمایشی باید مقدار قبل و بعد از درخواست رو مقایسه کنیم و ببینیم واقعاً چه تغییری انجام شده. ادعای امنیتی بدون بررسی اثر واقعی ناقصه.
چه شرطهایی لازم است تا یک عملیات در معرض CSRF قرار بگیرد؟
برای انجام حمله CSRF در سناریوی رایج مبتنی بر کوکی، چند شرط باید برقرار باشه: کاربر نشست معتبری داشته باشه، عملیات موردنظر وضعیت سایت رو تغییر بده، مرورگر بتونه اعتبارنامه لازم رو در اون شرایط بفرسته و دفاع ضد CSRF هم کافی نباشه. البته همه مدلهای احراز هویت یکسان نیستن و بعضی جریانهای ورود، خروج یا اتصال حساب، بررسی ویژه خودشون رو میخوان.
| شرط یا وضعیت | اثر در سناریو | برداشت درست |
|---|---|---|
| نشست معتبر در مرورگر | سرور میتواند کاربر را تشخیص دهد | هویت معتبر، قصد واقعی را ثابت نمیکند |
| عملیات تغییر وضعیت | مثلاً تغییر تنظیم غرفه | باید برای درخواستهای حساس بررسی ضد CSRF باشد |
| سیاست کوکی قابل اعمال | مرورگر شاید کوکی را بفرستد | SameSite میتواند حمله را محدود کند، نه اینکه همیشه تنها دفاع باشد |
| نبود یا ضعف کنترل قصد | توکن یا مبدأ بررسی نمیشود | ممکن است درخواست ناخواسته پذیرفته شود |
| اعتبارسنجی مجوز | نقش و شیء بررسی میشود | مجوز لازم است اما بهتنهایی دفاع ضد CSRF نیست |
پس هر وقت شنیدی «هک سایت بدون رمز»، دنبال جزئیات بگرد. منظور لزوماً کنترل کامل حساب یا سرور نیست؛ ممکنه یک عملیات مشخص، اون هم تحت شرایط محدود، ناخواسته انجام بشه. تا وقتی مرورگر، نوع درخواست و نتیجه سمت سرور مشخص نشده، نباید وقوع CSRF رو قطعی اعلام کنیم.
چرا POST بودن درخواست جلوی حمله را نمیگیرد؟
یکی از برداشتهای اشتباه درباره حمله CSRF اینه که «من همه فرمهای تغییر اطلاعات رو POST کردم، پس CSRF ندارم». نه؛ POST خودش هیچ توکن امنیتیای تولید نمیکنه. درست هست که کارهای تغییردهنده نباید با GET انجام بشن، اما سرور برای درخواست POST هم باید کنترل مناسب داشته باشه.
اگه برنامه با یک درخواست GET اطلاعات حساب رو عوض کنه، کار رو حتی خطرناکتر کرده؛ چون GET باید برای خواندن داده باشه، نه تغییرش. از طرف دیگه، سازوکارهایی مثل تغییر متد از طریق پارامتر مخفی هم نباید راه فرار از کنترل ضد CSRF بسازن. متد درست و دفاع درست، هر دو لازمن.
راستی این موضوع با SQL Injection یکی نیست. اونجا ورودیِ کنترلنشده ممکنه ساختار Query دیتابیس رو عوض کنه؛ اینجا درخواست میتونه از نظر مقدار کاملاً عادی باشه، ولی کاربر واقعاً قصد انجامش رو نداشته باشه. بنابراین Prepared Statement مشکل CSRF رو حل نمیکنه.

کوکی SameSite چیست و واقعاً چقدر جلوی هک سایت را میگیرد؟
برای کاهش خطر حمله CSRF، ویژگی SameSite روی کوکی مشخص میکنه مرورگر در چه درخواستهای بینسایتی اجازه ضمیمه کردنش رو داره. سه مقدار مهمش Strict، Lax و None هستن. تفاوتشون رو توی جدول ببین؛ فقط یادت باشه SameSite مربوط به سیاست کوکیه، نه بررسی مجوز کاربر.
| مقدار کوکی | رفتار کلی | نکته امنیتی |
|---|---|---|
| SameSite=Strict | کوکی در درخواستهای cross-site ارسال نمیشود | سختگیرانهتر؛ ممکن است بعضی جریانهای کاربری نیاز به طراحی متفاوت داشته باشند |
| SameSite=Lax | ارسال کوکی در بسیاری از درخواستهای cross-site محدود میشود، اما برخی ناوبریهای سطح بالا با روشهای امن استثنا دارند | باید همه مسیرهای تغییر وضعیت و استثناهای مرورگر بررسی شوند |
| SameSite=None; Secure | اجازه ارسال در زمینههای cross-site با شرط Secure را میدهد | تنها برای نیاز واقعی و با کنترلهای تکمیلی مناسب است |
خیلی از مرورگرها وقتی SameSite صریح تنظیم نشده باشه، رفتاری نزدیک به Lax دارن. اما این موضوع جزئیات مهمی داره؛ مثلاً بعضی مرورگرها برای کوکی تازهتنظیمشده، در حالت پیشفرض Lax یک استثنای کوتاه برای POST دارن. پس برای یک سایت واقعی به «مرورگر خودش مراقبه» تکیه نکن؛ تنظیم کوکی و مسیرهای تغییر وضعیت رو جداگانه آزمایش کن. برای جزئیات این استثنا، مستند Set-Cookie در MDN رو ببین.
دو ویژگی دیگه هم زیاد میبینی: Secure انتقال کوکی رو به ارتباط امن محدود میکنه و HttpOnly جلوی خواندن مستقیم اون کوکی توسط JavaScript رو میگیره. هر دو مهمن، اما نه جای SameSite هستن و نه جای توکن ضد CSRF. امنیت درخواست حاصل چند تصمیم مکمل در مرورگر و سروره.
تفاوت Same-Origin و Same-Site؛ نکتهای که خیلیها جا میاندازند
در تحلیل حمله CSRF یک دام مفهومی داریم: same-site و same-origin یکی نیستن. مثلاً دو زیردامنه متعلق به یک دامنه ثبتپذیر، در شرایط متعارف میتونن same-site باشن ولی چون hostname متفاوتی دارن، same-origin نباشن. برای بررسی دقیق باید scheme و دامنه ثبتپذیر رو هم حساب کنیم، نه فقط شباهت ظاهری آدرس رو. اگر خواستی بیشتر بخونی، راهنمای Same-Origin Policy در MDN رو ببین.
حالا فرض کن روی یکی از زیردامنهها محتوای کماعتماد یا سرویس شخص ثالث قرار گرفته. اگه پنل حساس فقط به SameSite دل بسته باشه، درخواستهایی که cross-origin ولی same-site هستن میتونن مسئلهساز بشن. برای همین زیردامنهها و سیاست بررسی مبدأ رو هم توی طراحی امنیت در نظر میگیریم.
برای تمرین لازم نیست سراغ زیردامنه واقعی کسی بری. روی محیط آزمایشگاهی خودت دو مبدأ بساز و در Developer Tools ببین مرورگر کوکی و هدر Origin رو در چه شرایطی میفرسته. این مشاهدهها خیلی بیشتر از حفظ کردن اسم تنظیمات کمکت میکنن.
توکن CSRF چیست و چرا از یک عدد ثابت بهتر است؟
یکی از روشهای شناختهشده برای جلوگیری از حمله CSRF استفاده از توکن CSRF هست. سرور یک مقدار تصادفی و غیرقابل پیشبینی رو به نشست معتبر مرتبط میکنه و فرم یا درخواست مورداعتماد اون رو همراه خودش میفرسته. موقع پردازش عملیات حساس هم باید مقدار دریافتشده با مقدار مورد انتظار بررسی بشه. اگر توکن لازم وجود نداشت یا درست نبود، درخواست باید رد بشه.
توکن دفاعی در برابر حمله CSRF رو نباید از تاریخ، نام کاربری یا یک رشته ثابت ساخت. برای تولیدش به مولد تصادفی رمزنگارانه نیاز داریم و برای اعتبارسنجیاش باید به وضعیت قابل اعتماد سمت سرور تکیه کنیم. همینقدر مهمه که همه مسیرهای تغییر وضعیت واقعاً این بررسی رو انجام بدن؛ یک endpoint فراموششده میتونه کل طراحی رو زیر سؤال ببره.
بعضی برنامهها یک توکن رو تا پایان نشست معتبر نگه میدارن و بعضی برای هر درخواست مقدار جدید میسازن. روش دوم در بعضی سناریوها مفیده ولی مدیریت چند تب، دکمه Back و فرمهای طولانی رو سختتر میکنه. برای همین بهتره از قابلیت آزمایششده فریمورک استفاده کنیم و بیدلیل سازوکار تازه اختراع نکنیم.
در الگوی Synchronizer Token، مقدار مورد انتظار معمولاً در وضعیت سمت سرور نگهداری و از طریق فرم یا هدر درخواست ارسال میشه؛ نباید اون رو بیدلیل داخل URL و لاگها پخش کنیم. در معماریهای بدون نشست سروری هم الگوی Signed Double-Submit Cookie مطرحه، ولی صرف برابر بودن دو مقدارِ قابلکنترل، دفاع کافی نیست. اتصال امن به زمینه نشست تفاوت اصلی رو میسازه. راهنمای OWASP این دو الگو رو با جزئیات مقایسه کرده.

آزمایش کاملاً آفلاین با Python : یک درخواست معتبر است، ولی قصد آن معتبر نیست
حالا بریم سراغ یک نمونه واقعی ولی کاملاً آفلاین. در این برنامه هیچ سرور، شبکه یا مرورگری در کار نیست؛ یک نشست ساختگی و یک فرم ساختگی داریم تا ببینیم سرور بدون بررسی توکن چه تصمیمی میگیره. نشست معتبر رو جدا از داده ارسالی فرض میکنیم؛ چون توی برنامه واقعی، کاربر نباید خودش بتونه هویت معتبرش رو با یک فیلد دلخواه تعیین کنه.
import secrets
# شبیهسازی نشست مورد تأیید سرور؛ ورودی مرورگر نیست.
server_session = {
"seller": "nastaran",
"csrf_token": secrets.token_urlsafe(32),
"payout_label": "original",
}
def update_payout(authenticated_session, form, *, require_csrf=True):
# در سرویس واقعی، نشست از احراز هویت سمت سرور به دست میآید.
if authenticated_session is not server_session:
return "401 | نشست نامعتبر"
if not isinstance(form, dict):
return "400 | ورودی نامعتبر"
new_label = form.get("new_label")
if not isinstance(new_label, str) or not (1 <= len(new_label) <= 50):
return "400 | ورودی نامعتبر"
if require_csrf:
supplied = form.get("csrf_token")
expected = server_session["csrf_token"]
if not isinstance(supplied, str) or not secrets.compare_digest(
expected, supplied
):
return "403 | درخواست رد شد"
server_session["payout_label"] = new_label
return "200 | تغییر ثبت شد"
unintended_form = {"new_label": "unexpected"}
print("بدون دفاع:", update_payout(server_session, unintended_form, require_csrf=False))
server_session["payout_label"] = "original"
print("با دفاع:", update_payout(server_session, unintended_form))
intended_form = {
"csrf_token": server_session["csrf_token"],
"new_label": "approved",
}
print("درخواست معتبر:", update_payout(server_session, intended_form))
print("نتیجه نهایی:", server_session["payout_label"])
بعد از اجرای فایل با Python 3، چهار خط زیر رو میبینی:
بدون دفاع: 200 | تغییر ثبت شد
با دفاع: 403 | درخواست رد شد
درخواست معتبر: 200 | تغییر ثبت شد
نتیجه نهایی: approvedخط اول عمداً حالت ناامن رو نشون میده: نشست معتبره و بدون توکن تغییر پذیرفته میشه. خط دوم با همون داده و توکنِ غایب رد میشه. خط سوم توکن صحیح داره و مجازه، و خط آخر هم ثابت میکنه مقدار نهایی همونی شده که انتظار داشتیم. این فقط مدل تصمیمگیریه، نه پیادهسازی آماده برای Production.
دقت کن مقدار new_label اصلاً دستور مخرب نیست؛ یک متن معمولیه. خطر از «چه چیزی فرستاده شده» شروع نمیشه، بلکه از «آیا این تغییر واقعاً مجاز و خواستهشده بوده» میاد. همین تفاوتیه که باید موقع تحلیل حمله CSRF یادت بمونه.
فقط توکن کافی است؟ ترکیب درست دفاعهای ضد CSRF
برای جلوگیری از حمله CSRF در یک سامانه واقعی، معمولاً چند لایه کنار هم لازم داریم: توکن معتبر، سیاست درست کوکی، بررسی مبدأ یا Fetch Metadata در شرایط مناسب، متد HTTP صحیح و کنترل مجوز عملیات. لازم نیست همهشون رو کورکورانه روشن کنیم؛ باید ببینیم معماری سایت چیه و کدوم دفاع برای اون مسیر حساس واقعاً کار میکنه.
| لایه دفاعی | چه مشکلی را هدف میگیرد؟ | اشتباه رایج |
|---|---|---|
| توکن همبسته با نشست | سخت کردن درخواست جعلی از مبدأ دیگر | قبول کردن درخواست بدون توکن یا توکن همگانی |
| SameSite و Secure | کنترل ضمیمه شدن کوکی در زمینههای مختلف | فرض اینکه SameSite همیشه تنها دفاع کافی است |
| Origin / Fetch Metadata | کمک به تشخیص زمینه ارسال درخواست | اعتماد کامل به هدر بدون سیاست روشن برای حالتهای غایب |
| متد مناسب | جلوگیری از تغییر وضعیت با GET | تصور اینکه POST بهتنهایی ضد CSRF است |
| مجوز سمت سرور | جلوگیری از عملیات خارج از اختیار کاربر | جایگزین کردن مجوز با توکن یا بالعکس |
| تأیید مجدد عملیات بسیار حساس | کاهش اثر درخواست ناخواسته پرخطر | تکیه بر پیام تأیید نمایشیِ قابل دورزدن |
مثلاً تغییر اطلاعات تسویه یا بازیابی حساب ممکنه علاوه بر توکن ضد CSRF به تأیید مجدد هم احتیاج داشته باشه. ولی اگه برای هر تغییر ساده یک OTP بفرستیم، کاربر رو کلافه میکنیم. سطح حفاظت باید با ریسک واقعی عملیات هماهنگ باشه.
Origin، Referer و Fetch Metadata دقیقاً چه کمکی میکنند؟
هنگام بررسی حمله CSRF، هدر Origin در خیلی از درخواستهای مرورگر به سرور میگه درخواست از چه مبدأیی اومده. Referer هم گاهی سرنخ مشابهی میده؛ اما ممکنه بهخاطر سیاست حریم خصوصی حذف یا کوتاه بشه. پس اگه از این هدرها برای تصمیم امنیتی استفاده میکنی، تکلیف مقدار غایب، null یا نامعتبر رو هم روشن کن؛ غیبت هدر نباید خودکار به معنی «مجاز» باشه.
هدرهایی مثل Sec-Fetch-Site به تشخیص same-origin، same-site و cross-site کمک میکنن. این اطلاعات میتونن درخواستهای مشکوک رو زودتر متوقف کنن، ولی برای مرورگرهای قدیمیتر و جریانهای خاص باید سیاست جایگزین مشخص باشه. هدف ما یک کنترل قابل پیشبینیه، نه شرطی که نصف کاربران قانونی رو بیدلیل مسدود کنه.
در مثال بازارچه، میتونیم برای تغییر تنظیمات هم توکن رو بررسی کنیم و هم در صورت نیاز مبدأ رو با Origin دقیق بسنجیم. چک کردن اینکه «اسم دامنه توی متن هدر هست» اصلاً روش قابل اعتمادی نیست؛ مقایسه باید براساس Origin واقعی و دامنه مورد انتظار انجام بشه.
CORS چه فرقی با CSRF دارد؟ یک سوءتفاهم مهم در هک سایت
توی آموزش حمله CSRF شاید شنیده باشی «CORS رو بستیم، پس CSRF نداریم». این برداشت اشتباهه. CORS بیشتر تعیین میکنه اسکریپت یک مبدأ در چه شرایطی میتونه پاسخ مبدأ دیگه رو بخونه و برای درخواستهای غیرساده چه پیششرطهایی برقرار باشه. اما یک فرم معمولی ممکنه بدون اینکه اسکریپت صفحه خارجی پاسخ رو بخونه، باعث ارسال درخواست بشه.
پس نه «نمیتونم پاسخ رو بخونم» یعنی عملیات انجام نشده، نه «درخواست ارسال شد» یعنی عملیات موفق بوده. باید سمت مرورگر و سرور هر دو رو ببینی: کوکی همراه درخواست بوده؟ پیشپرواز لازم بوده؟ سرور درخواست رو پذیرفته؟ داده نهایی تغییر کرده؟ اینها شواهد واقعی تحلیل هستن.
در APIهایی که فقط JSON و هدر اختصاصی میپذیرن، بعضی سناریوهای کلاسیک CSRF سختتر میشن. ولی تنظیم اشتباه CORS، پذیرش Content-Typeهای ناخواسته یا مسیرهای جایگزین میتونن دوباره مسئله ایجاد کنن. بهتره به رفتار واقعی API تکیه کنیم، نه به اسم REST یا JSON.
چرا هک با XSS و CSRF یکسان نیست؟ فرقشان را با یک مثال بفهم
برای فهم تفاوت CSRF و XSS، توی درس XSS نابغهها دیدیم ورودی نامطمئن ممکنه توی مرورگر به کد قابل اجرا تبدیل بشه. اما در حمله CSRF معمولاً لازم نیست کدی داخل سایت هدف اجرا بشه؛ ممکنه یک درخواست از مبدأ دیگه شروع بشه و با نشست معتبر کاربر پردازش بشه. برای همین راه رفع این دو باگ هم یکسان نیست.
| باگ امنیتی | اشکال اصلی | دفاع مهم |
|---|---|---|
| CSRF | درخواست حساس، قصد معتبر ندارد | توکن ضد CSRF، SameSite و کنترل مبدأ |
| XSS | داده نامطمئن در مرورگر تبدیل به کد میشود | کدگذاری خروجی متناسب با زمینه و API امن |
| IDOR | کاربر روی رکورد مشخص مجوز ندارد | بررسی مجوز هر شیء سمت سرور |
| SQL Injection | داده وارد ساختار دستور پایگاه داده میشود | Query پارامتری و Prepared Statement |
البته این دو مسئله میتونن روی هم اثر بذارن. اگر XSS در همون مبدأ سایت وجود داشته باشه، اسکریپت ناخواسته ممکنه به توکنهای قابل دسترس در صفحه یا جریان درخواست دسترسی پیدا کنه. از اون طرف، برای فهم ضعف مجوز سطح رکورد، درس IDOR و برای خطاهای ساخت Query، درس SQL Injection رو بخون. هر کدوم ریشه جداگانهای دارن.
چرا توکن اشتباه طراحیشده ممکنه فقط حس امنیت بدهد؟
توکن ثابت، عمومی یا قابل حدس
اولین ایراد جدی، توکن ثابت یا قابل حدسه. اگه همه کاربران مقدار یکسانی داشته باشن، دفاع عملاً معنای خودش رو از دست میده. توکن باید به یک منبع تصادفی مطمئن و زمینه نشست معتبر متکی باشه.
اعتبارسنجی ناقص روی یکی از مسیرها
اشتباه بعدی وقتیه که صفحه فرم توکن داره اما API دیگهای همون تغییر رو بدون بررسی انجام میده. یا برنامه فقط وقتی توکن وجود داره اعتبارسنجی میکنه و وقتی نیست کلاً از شرط میگذره! همه مسیرهای حساس باید سیاست یکسان و صریح داشته باشن.
توکن نامرتبط با نشست یا انتقال ناامن
فرض کن برنامه فقط دو فیلدی رو که خود درخواست فرستاده با هم مقایسه کنه. این بررسی هیچ چیزی درباره معتبر بودن درخواست ثابت نمیکنه. سرور باید توکن رو به یک مقدار مورد انتظارِ قابل اعتماد وصل کنه؛ توکن افشاشده در URL یا لاگ هم میتونه کل دفاع رو خراب کنه.
توکن ضد CSRF را با مجوز کاربر اشتباه نگیریم
حتی وقتی توکن درسته، باز هم باید بپرسیم این کاربر اجازه تغییر همین داده رو داره یا نه. نسترن نباید با توکن معتبر خودش غرفه بقیه رو ویرایش کنه. این همون تفاوت قصد و اختیاره که در مقاله IDOR و کنترل دسترسی توضیح دادیم.
حمله CSRF در وردپرس؛ نقش nonce واقعاً چیست؟
در بحث امنیت وردپرس، برای کاهش احتمال حمله CSRF یک ابزار مهم داریم به اسم nonce. توی افزونهها و فرمهای مدیریتی معمولاً با wp_nonce_field()، check_admin_referer() یا check_ajax_referer() سراغش میریم. ولی اسمش گولزنندهست: Nonce وردپرس واقعاً یکبارمصرف نیست و نه احراز هویت انجام میده و نه جای بررسی مجوز رو میگیره.
برای اینکه فقط چند تابع پراکنده نبینی، مثال وردپرس رو به دو بخش بههموصل تقسیم کردم: یک فرم مدیریتی با action مشخص، و یک Handler که درخواست همون فرم رو تحویل میگیره. خود Handler متد POST، مجوز مدیر، Nonce و معتبر بودن داده رو بررسی میکنه. هیچ تنظیم واقعیای ذخیره نمیکنیم تا کد صرفاً آموزشی بمونه.
<?php
// این بخش را فقط در خروجی یک صفحه مدیریتی آزمایشی وردپرس قرار بده.
if ( current_user_can( 'manage_options' ) ) : ?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<input type="hidden" name="action" value="nb_demo_update_notice">
<?php wp_nonce_field( 'nb_demo_update_notice', 'nb_demo_nonce' ); ?>
<label for="notice_text">متن اعلان آزمایشی</label>
<input id="notice_text" name="notice_text" type="text" maxlength="100" required>
<button type="submit">بررسی درخواست آزمایشی</button>
</form>
<?php endif; ?><?php
// این Handler فقط در افزونه/محیط تست وردپرس تعریف میشود.
add_action( 'admin_post_nb_demo_update_notice', function () {
if ( 'POST' !== ( $_SERVER['REQUEST_METHOD'] ?? '' ) ) {
wp_die( 'روش درخواست مجاز نیست.', '', [ 'response' => 405 ] );
}
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'مجوز کافی ندارید.', '', [ 'response' => 403 ] );
}
check_admin_referer( 'nb_demo_update_notice', 'nb_demo_nonce' );
if ( ! isset( $_POST['notice_text'] ) || ! is_string( $_POST['notice_text'] ) ) {
wp_die( 'ورودی نامعتبر است.', '', [ 'response' => 400 ] );
}
$notice = sanitize_text_field( wp_unslash( $_POST['notice_text'] ) );
if ( '' === trim( $notice ) || strlen( $notice ) > 400 ) {
wp_die( 'طول متن مجاز نیست.', '', [ 'response' => 400 ] );
}
// صرفاً نمایش نتیجه در محیط تست؛ هیچ تنظیمی ذخیره نمیشود.
wp_die( esc_html( 'کنترلها موفق بودند. تغییر واقعی ثبت نشد.' ), 'CSRF Demo', [ 'response' => 200 ] );
} );نکته مهم اینه که حتی اگر فرم از پنل مدیر باز بشه، Handler باز هم باید از اول همه کنترلها رو انجام بده. current_user_can() درباره اختیار کاربر تصمیم میگیره و check_admin_referer() Nonce مخصوص عملیات رو بررسی میکنه؛ هیچکدوم جای اون یکی نیستن. این دو قطعه نمونه آموزشی برای محیط تست وردپرس هستن و روی سایت اصلی نباید بدون بررسی و اتصال درست استفاده بشن.
برای درخواستهای AJAX وردپرس هم معمولاً check_ajax_referer() رو میبینی؛ اونجا هم مجوز مستقل لازمه. در REST API مبتنی بر کوکی، سازوکار احراز هویت و Nonce مربوط به REST در کنار permission_callback مطرحه. مستندات Endpointهای اختصاصی وردپرس رو کنار کد خودت باز نگه دار؛ پنهان بودن مسیر API محافظت نیست.
در REST API، فریمورکهای مدرن و اپلیکیشنهای تکصفحهای چه فرقی دارد؟
اینکه سایتت SPA یا REST API داره، حمله CSRF رو خودکار حذف نمیکنه. سؤال اصلی اینه که مرورگر اعتبارنامه رو چطوری میفرسته. وقتی احراز هویت مبتنی بر کوکی و ارسال خودکاره، دفاع ضد CSRF هنوز مهمه. اگر اعتبارنامه فقط با هدر اختصاصی و بهشکل غیرخودکار ارسال میشه، مدل خطر فرق میکنه؛ ولی امنیت توکن و XSS همچنان سر جاشه.
توی برنامههای جاوااسکریپتی میشه از هدر اختصاصی، توکن معتبر و سیاست درست CORS استفاده کرد. فقط مواظب باش توکن حساس رو ناخواسته به مبدأهای دیگه نفرستی. همچنین هر API باید مستقل از بحث CSRF، مجوز انجام همون عملیات رو بررسی کنه.
مثلاً در وردپرس، permission_callback برای ارزیابی دسترسی Endpoint لازمه، ولی خودش سازوکار احراز هویت مبتنی بر کوکی رو جایگزین نمیکنه. مستند WordPress REST API این موضوع رو توضیح داده. توی طراحی امن، درخواست باید هم از نظر هویت معتبر باشه و هم از نظر اختیار و دفاع متناسب با CSRF.
آیا تایید دومرحلهای و رمز عبور قوی جلوی CSRF را میگیرند؟
رمز قوی و 2FA برای سختتر کردن ورود غیرمجاز خیلی مفیدن، اما حمله CSRF درباره یک نشستیه که از قبل معتبر شده. بنابراین فعال بودن احراز هویت دومرحلهای باعث نمیشه درخواستهای تغییر تنظیمات رو بدون کنترل اضافی بپذیریم.
برعکسش هم درسته: توکن ضد CSRF نمیتونه ضعف رمز، نشست ناامن یا دسترسی بیحساب به اطلاعات بقیه رو جبران کنه. باید هر لایه امنیتی کار خودش رو درست انجام بده؛ قرار نیست یک تنظیم جادویی همه ضعفها رو بپوشونه.
چه عملیاتهایی در یک سایت فروشگاهی یا پنل کاربری حساستر هستند؟
روی یک فروشگاه یا پنل واقعی، عوض شدن نام نمایشی با تغییر حساب تسویه اصلاً یک ریسک نداره. قبل از طراحی دفاع بهتره عملیات رو بر اساس اثر احتمالی دستهبندی کنیم. تغییر آدرس تحویل، ایمیل بازیابی، سطح دسترسی و عملیات گروهی معمولاً حساستر از یک تنظیم ظاهری هستن.
| نمونه عملیات | اثر احتمالیِ تغییر ناخواسته | کنترل تکمیلی قابل بررسی |
|---|---|---|
| تغییر نام نمایشی | سردرگمی یا آسیب محدود به پروفایل | توکن و اعتبارسنجی معمول |
| تغییر ایمیل بازیابی | اختلال در بازیابی حساب و مالکیت | تأیید کانال جدید و اعلان به قبلی |
| تغییر اطلاعات تسویه | پیامد مالی و اختلاف حساب | مجوز ویژه، ضد CSRF و تأیید مستقل |
| افزودن کاربر با نقش مدیریتی | گسترش دسترسی غیرمجاز | کنترل نقش، تأیید مجدد و ثبت رویداد |
| ثبت عملیات گروهی روی سفارشها | تغییر وضعیت تعداد زیادی رکورد | مجوز سطح شیء و دفاع ضد درخواست جعلی |
برای تغییرهای خیلی حساس شاید تأیید کانال جداگانه، احراز هویت مجدد یا ثبت رویداد لازم بشه. ولی این تصمیم باید با ریسک هماهنگ باشه، نه اینکه برای هر کلیک ساده یک پیامک بفرستیم. امنیت خوب جلوی سوءاستفاده رو میگیره و کار مجاز رو هم بیدلیل سخت نمیکنه.
چطور در تست نفوذ قانونی، یک مورد CSRF را بررسی و گزارش کنیم؟
در تست نفوذ وب، برای بررسی قانونی حمله CSRF اول فهرست عملیات تغییر وضعیت رو بنویس و برای هرکدوم مشخص کن چه اعتبارنامهای همراه درخواست میره و چه دفاعی باید وجود داشته باشه. بعد فقط روی سامانه آزمایشی یا محدوده دارای مجوز، فرضیه رو بررسی کن. هدف پیدا کردن رفتار واقعیه، نه مجبور کردن ابزار به نمایش یک هشدار قرمز.
بهتره دو حساب و چند داده ساختگی داشته باشی تا بتونی قبل و بعد رو مقایسه کنی. وضعیت کوکی، SameSite، هدرهای درخواست، لاگ و نتیجه تغییر رو ثبت کن. اگر دفاع کار کرد و درخواست غیرمجاز رد شد، اون هم نتیجه مهم و قابل گزارشیه.
گزارش خوب بهجای «هک کامل سایت»، دقیق میگه کدوم عملیات با چه پیششرطهایی ناخواسته اجرا شده و چه محدودیتهایی وجود داشته. از افشای توکن و اطلاعات واقعی کاربران خودداری کن و روش بازتولید رو فقط در اختیار تیم مجاز بذار. اگه با مراحل گزارشنویسی آشنا نیستی، مراحل تست نفوذ رو هم مرور کن.
چرا بعضی هشدارهای اسکنر درباره CSRF اشتباهاند؟
اگه اسکنر هشدار حمله CSRF داد و نوشت «فرم بدون توکن CSRF»، هنوز نتیجه قطعی نگرفتیم. شاید فرم عمومی باشه، وضعیت رو عوض نکنه، احراز هویت خودکار نداشته باشه یا کنترل مبدأ جلوی درخواست جعلی رو بگیره. باید به شکل امن، پیششرطها و اثر واقعی رو بررسی کرد.
همینطور صرف وجود یک فیلد به اسم csrf_token هم نشونه امنیت نیست. ممکنه سرور اصلاً اون رو چک نکنه یا یکی از مسیرها بررسی رو دور بزنه. این تفاوت بین سرنخ و اثبات رو در درس اسکن آسیبپذیری هم دیدیم.
چه اشتباههایی دفاع سایت را در عمل بیاثر میکنند؟
توی دفاع در برابر حمله CSRF، اشتباههایی که زیاد تکرار میشن مشخصن: اضافه کردن فیلد مخفی بدون اعتبارسنجی سمت سرور، بررسی نکردن همه Endpointها، استفاده از توکن ثابت و فراموش کردن Authorization. هر کدوم میتونه دفاعی رو که روی کاغذ کامل به نظر میرسه، در عمل ناقص کنه.
یک ایراد کمتر دیدهشده هم اعتماد بیقید به زیردامنههاست. SameSite لزوماً بین همه زیردامنههای همسایت مرز امنیتی مورد نیازت رو ایجاد نمیکنه. ضمناً چک کردن فقط با JavaScript سمت کاربر کافی نیست؛ تصمیم نهایی باید جایی باشه که درخواستکننده نتونه شرط رو حذف کنه.
از طرف دیگه، اگر توکنها هر چند ثانیه از اعتبار بیفتن، کاربر توی فرم طولانی و چند تب مدام خطا میگیره. باید با امنیت و تجربه کاربری با هم فکر کنیم؛ تست چند تب، بازگشت مرورگر و نشستهای همزمان بخشی از همین کاره.
پنج تمرین تحلیلی : حالا خودت نقش ارزیاب امنیت را بازی کن
برای اینکه تحلیل حمله CSRF واقعاً توی ذهنت بمونه، پنج موقعیت فرضی توی جدول داریم. قبل از دیدن جوابها از خودت بپرس «این درخواست کدوم امضا رو کم داره؟» و «چه چیزی هنوز اثبات نشده؟». هیچکدوم نیاز به اجرای حمله روی سایت واقعی ندارن.
| موقعیت ساختگی | سؤال درست | تحلیل پیشنهادی |
|---|---|---|
| فرم تغییر شماره تماس، توکن ندارد | آیا درخواست ناخواسته قابل اجراست؟ | وضعیت کوکی، کنترل مبدأ، متد و اثر واقعی را بررسی کن |
| فرم توکن دارد ولی روی همه حسابها مقدار ثابت است | آیا توکن اثبات قصد میکند؟ | به نشستبندی، تصادفی بودن و اعتبارسنجی سرور شک کن |
| پنل فقط با POST تغییر میدهد | آیا پس کاملاً امن است؟ | خیر؛ POST توکن یا کنترل مبدأ نیست |
| همه کوکیها HttpOnly هستند | آیا CSRF رفع شده؟ | خیر؛ HttpOnly برای محدود کردن خواندن کوکی توسط اسکریپت است |
| درخواست بدون توکن 403 میدهد | آیا باگ وجود ندارد؟ | این آزمون سالم است؛ برای نتیجه کلی مسیرهای مشابه و سیاستهای دیگر هم لازماند |
مثلاً فرم تغییر تلفن رو در نظر بگیر: ممکنه کاربر وارد شده باشه و اجازه تغییر تلفن خودش رو هم داشته باشه، اما اگر سازوکار تأیید درخواست ناقص باشه، خطر CSRF هنوز مطرحه. در مقابل، توکن صحیح بهتنهایی بهش اجازه تغییر شماره فرد دیگه رو نمیده. همین دو جمله چکیده مدل سه امضاست.
بعد از این درس، مسیر درست یادگیری هک سایت چیست؟
اگه تازه وارد این حوزه شدی، از درس هک سایت چگونه انجام میشود؟ شروع کن، بعد تفاوت تزریق SQL، XSS و IDOR رو در کنار CSRF یاد بگیر. وقتی ریشه هر کدوم رو بفهمی، اسم ابزارها کمتر گیجت میکنن.
این مقاله قرار نیست جای تجربه عملی کامل رو بگیره. توی مسیر حرفهای باید کار با HTTP، مرورگر، مدل نشست، بررسی مجوز و گزارشنویسی رو هم تمرین کنی. برای دیدن سرفصلهای پیشرفتهتر هم میتونی پکیج متخصص تست نفوذ نابغهها رو بررسی کنی؛ بدون اینکه تصور کنیم دیدن یک دوره بهتنهایی مهارت عملی رو تضمین میکنه.
اگه میخوای جلوی حمله CSRF در سایت خودت رو بگیری، لازم نیست همه ابزارهای هک رو بلد باشی. از فرمهای تغییر حساب، تنظیمات مدیریتی، سفارشها و مسیرهای API شروع کن و ببین آیا سرور مجوز و دفاع ضد درخواست جعلی رو درست بررسی میکنه یا نه. این مرور کد معمولاً از نصب عجولانه یک افزونه جدید مفیدتره.

جمعبندی : سایت امن، به هر درخواست معتبر اعتماد کورکورانه نمیکند
اگه بخوام تمام درس رو توی یک جمله بگم: نشست معتبر به معنی قصد معتبر نیست. حمله CSRF دقیقاً از فاصله بین این دو استفاده میکنه. برای دفاع، باید بدونیم مرورگر چی میفرسته، سرور چی رو بررسی میکنه و کدوم عملیات واقعاً اجازه اجرا داره.
دفعه بعد که یک فرم مدیریتی نوشتی یا درخواست تغییر اطلاعات رو توی آزمایشگاه بررسی کردی، این سؤال رو از خودت بپرس: «سرور از کجا میفهمه صاحب نشست واقعاً این تغییر رو از جریان درست شروع کرده؟» جواب فنی روشن به این سؤال، از حفظ کردن چند اصطلاح امنیتی خیلی ارزشمندتره.
سؤالات متداول درباره حمله CSRF
حمله CSRF چیست و چه معنیای دارد؟
حمله CSRF یا جعل درخواست بینسایتی یعنی در شرایطی مشخص، مرورگر یک کاربر واردشده باعث ارسال درخواست ناخواسته به سایت اصلی میشه و سایت هم به خاطر دفاع ناکافی اون عملیات رو میپذیره. شرط مهم، معتبر بودن وضعیت احراز هویت و نقص کنترل درخواست حساسه.
آیا با CSRF میتوان بدون داشتن رمز، سایت را هک کرد؟
بله، در بعضی شرایط مهاجم میتونه بدون دونستن رمز و بدون خوندن کوکی، از مرورگرِ کاربرِ واردشده برای ارسال یک درخواست سوءاستفاده کنه. اما این اتفاق به تنظیم کوکی و دفاعهای خود سایت بستگی داره؛ هر بازدیدی از یک سایت خارجی معادل حمله موفق نیست.
فرق CSRF با XSS چیست؟
توی XSS، داده نامطمئن ممکنه داخل مرورگر به کد قابل اجرا تبدیل بشه؛ اما در CSRF مشکل اینه که یک درخواست ناخواسته از طرف نشست معتبر اجرا میشه. این دو باگ گاهی روی هم اثر میذارن ولی روش دفاع اصلیشون فرق داره.
آیا SameSite=Lax بهتنهایی برای جلوگیری از CSRF کافی است؟
نه همیشه. SameSite در خیلی از سناریوها دفاع مهمیه، اما رفتار مرورگر، ناوبریها، زیردامنهها و مسیرهای تغییر وضعیت میتونن محدودیتهایی ایجاد کنن. بهتره اون رو بخشی از دفاع چندلایه ببینیم.
آیا استفاده از متد POST باعث جلوگیری از CSRF میشود؟
خیر. POST متد مناسبی برای تغییر وضعیت هست، اما ثابت نمیکنه کاربر خودش درخواست رو شروع کرده. برای عملیات حساس باید کنترل ضد CSRF و مجوز مناسب هم وجود داشته باشه.
در وردپرس nonce همان توکن ضد CSRF است؟
در وردپرس از Nonceهای مخصوص عملیات کمک میگیریم، مثلاً با wp_nonce_field() و check_admin_referer(). البته Nonce وردپرس واقعاً یکبارمصرف نیست و جای current_user_can() رو نمیگیره.
آیا فعال کردن ورود دومرحلهای جلوی حمله CSRF را میگیرد؟
نه؛ 2FA بیشتر از مرحله ورود محافظت میکنه. بعد از اینکه نشست ساخته شد، درخواستهای تغییر تنظیمات هنوز به دفاع ضد CSRF و بررسی مجوز نیاز دارن.
از کجا بفهمیم یک فرم واقعاً آسیبپذیری CSRF دارد؟
نه. نبود فیلد توکن فقط یک نشونه برای بررسیه. برای نتیجه قطعی باید ببینیم عملیات حساسه یا نه، اعتبارنامه چطور ارسال میشه و دفاعهای دیگه چه رفتاری دارن.
منابع معتبر برای بررسی و مطالعه بیشتر
برای شروع، راهنمای پیشگیری از CSRF در OWASP رو بخون. توضیح رفتار کوکی و محدودیتهای مرورگر هم در مستند MDN درباره CSRF اومده و آموزش PortSwigger مثالهای آموزشی دقیقتری داره.
برای کدنویسی وردپرس، مستند رسمی Nonce API و صفحه تابع check_admin_referer رو ببین. ضمن اینکه OWASP Top 10:2025، ضعف CSRF با شناسه CWE-352 رو در گروه Broken Access Control آورده. سناریوی هزاررنگ و مدل سه امضا فقط مثالهای اختصاصی همین آموزش هستن.