محافظت از فرم‌ها در برابر CSRF یکی از بنیادی‌ترین لایه‌های امنیتی هر برنامه وب است که بدون آن، مرورگر کاربر به ابزاری برای اجرای درخواست‌های ناخواسته تبدیل می‌شود. CSRF (Cross-Site Request Forgery) از اعتماد خودکار مرورگر به کوکی‌های نشست سوءاستفاده می‌کند و کاربری که در یک سایت احراز هویت شده را وادار به ارسال درخواستی می‌کند که خودش آن را طراحی نکرده است. دفاع مؤثر در برابر این حمله ترکیبی از توکن همگام، ویژگی SameSite، بررسی Origin و هدرهای امنیتی است. در ادامه، مکانیزم دقیق حمله، الگوهای پیاده‌سازی، اشتباهات رایج و روش‌های تست و اعتبارسنجی در سطح مهندسی ارشد بررسی می‌شود. هدف نهایی، ساخت فرم‌هایی است که هم امن باشند و هم تجربه کاربری و معماری بدون وضعیت را تخریب نکنند.

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

CSRF چیست و چه لایه‌ای را هدف می‌گیرد

CSRF یا Cross-Site Request Forgery نوعی حمله است که در آن مهاجم، کاربر احراز هویت‌شده را وادار می‌کند درخواستی را که خودش طراحی کرده، در بستر یک برنامه وب معتبر ارسال کند. نکته کلیدی این است که مهاجم نیازی به دزدیدن کوکی یا رمز عبور ندارد؛ مرورگر به‌صورت خودکار کوکی‌های مرتبط با دامنه هدف را به همراه درخواست ارسال می‌کند و سرور، آن درخواست را به‌عنوان یک درخواست معتبر از سوی کاربر می‌پذیرد.

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

این تفاوت ظریف را باید از حملات دیگر جدا کرد. در حملات XSS، مهاجم کد اجرا می‌کند و کوکی یا توکن را می‌دزدد. در SQL Injection، مهاجم از طریق ورودی کاربر به دیتابیس نفوذ می‌کند. اما در CSRF، مهاجم هیچ‌کدام از این کارها را انجام نمی‌دهد؛ بلکه کاربر را قربانی می‌کند تا خودش درخواست را ارسال کند. به همین دلیل، این حمله در دسته‌ی حملات Confused Deputy قرار می‌گیرد؛ کاربر در نقش یک واسطه‌ی ناخواسته ظاهر می‌شود که اختیاراتش را در خدمت مهاجم قرار می‌دهد.

جایگاه CSRF در OWASP Top 10

هرچند در نسخه‌های اخیر OWASP Top 10، CSRF زیرمجموعه‌ی «Broken Access Control» و «Security Misconfiguration» قرار گرفته و به‌عنوان یک دسته‌ی مستقل حذف شده، این تغییر به معنای کاهش اهمیت آن نیست. دلیل حذف این بود که فریم‌ورک‌های مدرن و مرورگرها بخش بزرگی از دفاع را درونی کرده‌اند؛ اما همین درونی‌سازی، خطر پنهان‌تری ایجاد کرده است: توسعه‌دهنده‌ای که نمی‌داند دفاع در کدام لایه اعمال می‌شود، ممکن است با یک تغییر کوچک، آن را غیرفعال کند.

چرا مرورگر بستر اصلی این حمله است

مرورگر برای تجربه‌ی وب مدرن، کوکی‌ها را در هر درخواست به دامنه‌ی مرتبط ارسال می‌کند. این رفتار، ستون فقرات نشست‌های کاربری است، اما همزمان نقطه‌ی ضعف بنیادین CSRF نیز به شمار می‌رود. از دید سرور، درخواستی که از یک <img> روی سایت مهاجم ارسال می‌شود، تفاوت قابل‌تشخیصی با درخواستی که از فرم سایت خودی ارسال شده، ندارد، مادامی که کوکی نشست همراه آن باشد.

Same-Origin Policy یا SOP از دسترسی جاوااسکریپت یک دامنه به محتوای دامنه دیگر جلوگیری می‌کند، اما ارسال درخواست را مسدود نمی‌کند. این نکته اغلب اشتباه فهمیده می‌شود. SOP اجازه‌ی خواندن پاسخ یک درخواست Cross-Origin را نمی‌دهد، ولی خود درخواست می‌تواند ارسال شود و اثر جانبی خود را روی سرور بگذارد. CSRF دقیقاً در همین شکاف زندگی می‌کند.

در CSRF، مهاجم به پاسخ نیازی ندارد؛ او فقط به اثر جانبی درخواست نیاز دارد. مرورگر درخواست را می‌فرستد، سرور آن را می‌پذیرد، و کاربر حتی متوجه نمی‌شود.

نکته‌ی مهم دیگر این است که مکانیزم‌های امن‌سازی نشست کاربر هرچند ضروری هستند، اما به‌تنهایی CSRF را دفع نمی‌کنند. حتی یک کوکی HttpOnly و Secure هم به‌صورت خودکار ارسال می‌شود، مگر آنکه ویژگی SameSite به‌درستی تنظیم شده باشد.

مکانیزم دقیق حمله، گام‌به‌گام

برای درک دقیق‌تر، سناریوی زیر را در نظر بگیرید. فرض کنید bank.example یک endpoint برای انتقال وجه دارد:

POST /transfer
Host: bank.example
Cookie: session=ABC123

amount=1000&to=attacker_account

کاربر در یک تب دیگر، وارد سایت مهاجم می‌شود. سایت مهاجم یک صفحه‌ی HTML ساده است که یک فرم مخفی دارد و با جاوااسکریپت، بلافاصله آن را ارسال می‌کند:

<form action="https://bank.example/transfer" method="POST" id="f">
  <input type="hidden" name="amount" value="1000">
  <input type="hidden" name="to" value="attacker_account">
</form>
<script>document.getElementById("f").submit();</script>

مرورگر کاربر، کوکی session=ABC123 را به همراه این درخواست ارسال می‌کند. سرور، کوکی را معتبر می‌بیند و درخواست را اجرا می‌کند. هیچ‌کدام از SOP یا مرزهای دامنه مانع این اتفاق نشده‌اند، چون SOP ارسال درخواست را ممنوع نمی‌کند.

مراحل دقیق این حمله را می‌توان به شکل زیر خلاصه کرد:

  1. کاربر در دامنه هدف احراز هویت شده و کوکی نشست فعال دارد.
  2. کاربر یک صفحه یا ایمیل تحت کنترل مهاجم را باز می‌کند.
  3. صفحه‌ی مهاجم درخواستی را به دامنه هدف ارسال می‌کند.
  4. مرورگر کوکی نشست را به‌صورت خودکار ضمیمه می‌کند.
  5. سرور درخواست را معتبر می‌داند و اثر جانبی را اجرا می‌کند.

تفاوت CSRF و XSS از دید مکانیزمی

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

برای مطالعه‌ی جزئیات بیشتر درباره‌ی این دسته از حملات و مبانی نظری آن، پست CSRF چیست و چگونه دفع می‌شود منبع پایه‌ی مناسبی است.

انواع حمله CSRF

CSRF یک حمله‌ی واحد نیست؛ بسته به سطح دسترسی، متد HTTP و بستر اجرا، اشکال گوناگونی پیدا می‌کند. شناخت این اشکال، پیش‌نیاز طراحی دفاع چندلایه است.

1. CSRF مبتنی بر GET

در این حالت، endpoint هدف با متد GET قابل اجراست و تنها با یک <img src="..."> یا <iframe> می‌توان درخواست را ارسال کرد. این ساده‌ترین شکل حمله است و به‌همین دلیل، اصل معماری امنیتی می‌گوید: هیچ endpoint تغییردهنده‌ی حالتی نباید با GET کار کند.

2. CSRF مبتنی بر POST

نسبت به GET پیچیده‌تر است، چون مهاجم نمی‌تواند به‌سادگی از تگ <img> استفاده کند، اما یک فرم خودکار یا یک fetch از دامنه‌ی مهاجم همچنان کار می‌کند، مادامی که مرورگر کوکی را ارسال کند.

3. Login CSRF

نوع خاصی که در آن مهاجم کاربر را مجبور می‌کند با حساب خودِ مهاجم وارد شود. نتیجه این است که فعالیت‌های بعدی کاربر در حساب مهاجم ثبت می‌شود. این حالت، در سیستم‌هایی که داده‌های حساس مانند آدرس، تاریخچه‌ی جستجو یا سبد خرید را ذخیره می‌کنند، بسیار خطرناک است.

4. Stored CSRF

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

5. JSON CSRF

سرویس‌هایی که endpoint آن‌ها Content-Type: application/json را انتظار دارد، در نگاه اول ایمن به نظر می‌رسند، چون ارسال Cross-Origin با این Content-Type از طریق فرم‌های HTML امکان‌پذیر نیست. اما با استفاده از navigator.sendBeacon، fetch با حالت خاص، یا سوءاستفاده از پلی‌فرم‌های مرورگر، این محدودیت دور زده می‌شود. راه‌حل مقاوم، بررسی دقیق هدر Origin و الزام به یک هدر سفارشی مانند X-Requested-With یا X-CSRF-Token است، زیرا مرورگر برای درخواست‌های Cross-Origin از افزودن خودکار این هدرها خودداری می‌کند.

چرا SameSite=Lax تنها راه‌حل نیست

ویژگی SameSite روی کوکی‌ها، در چند سال اخیر به یک خط دفاعی پیش‌فرض تبدیل شده است. مقدار Lax کوکی را فقط در درخواست‌های Same-Site و ناوبری سطح بالا (top-level navigation) با متد GET ارسال می‌کند. این تنظیم، بسیاری از سناریوهای کلاسیک CSRF را می‌بندد، اما نه همه‌ی آن‌ها:

  • در برخی مرورگرهای قدیمی یا حالت‌های سازگاری، رفتار متفاوتی وجود دارد.
  • در جریان‌هایی مانند SSO که نیاز به ارسال کوکی در Context شخص ثالث دارند، ممکن است SameSite=None; Secure لازم شود و این دقیقاً همان نقطه‌ای است که CSRF بازمی‌گردد.
  • زیردامنه‌های یک دامنه‌ی والد، از نظر SameSite هم‌سایت محسوب می‌شوند. اگر یکی از زیردامنه‌ها آسیب‌پذیر باشد، CSRF از آن مسیر ممکن می‌شود.

SameSite یک لایه‌ی دفاعی ارزشمند است، اما جایگزین توکن ضد CSRF نمی‌شود. هرکدام از این دو، بخشی از سطح حمله را می‌پوشانند و ترکیب آن‌ها Defense in Depth واقعی می‌سازد.

در پروژه‌هایی که با احراز هویت خارجی و SSO سروکار داریم، مرز میان SameSite و CSRF بسیار باریک است و باید با دقت معماری شود. مطالعه‌ی OAuth چیست و چگونه کار می‌کند می‌تواند در این زمینه دید بهتری بدهد.

الگوهای دفاعی در برابر CSRF

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

1. Synchronizer Token Pattern

در این الگو، سرور برای هر نشست یک توکن تصادفی تولید و در سمت سرور ذخیره می‌کند. توکن در فرم‌ها به‌عنوان یک فیلد مخفی قرار می‌گیرد و در هر ارسال، سرور آن را با مقدار ذخیره‌شده مقایسه می‌کند. این الگو، مقاوم‌ترین شکل دفاع است، چون مهاجم نه به توکن دسترسی دارد و نه می‌تواند مقدار آن را جعل کند.

// PHP
function csrf_token() {
    if (empty($_SESSION["csrf_token"])) {
        $_SESSION["csrf_token"] = bin2hex(random_bytes(32));
    }
    return $_SESSION["csrf_token"];
}

function verify_csrf($token) {
    return hash_equals($_SESSION["csrf_token"] ?? "", $token ?? "");
}

2. Double Submit Cookie

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

3. Signed Double Submit

نسخه‌ی مقاوم‌تر Double Submit که توکن را با HMAC امضا می‌کند. سرور توکن را از کوکی می‌خواند، امضا را بررسی می‌کند و اگر مقدار جعلی باشد، درخواست را رد می‌کند. این الگو در برابر زیردامنه‌های مخرب مقاوم است و در چارچوب‌هایی مانند Django و Angular به‌صورت پیش‌فرض پیاده‌سازی شده است.

4. بررسی Origin و Referer

سرور می‌تواند هدر Origin یا Referer را بررسی کند و اگر از دامنه‌ی مورد انتظار نبود، درخواست را رد کند. این روش سبک و کم‌هزینه است، اما نباید تنها لایه‌ی دفاع باشد، چون در برخی سناریوها این هدرها ارسال نمی‌شوند یا در Proxyهایی خاص، مقداری متفاوت پیدا می‌کنند.

5. Custom Headers

الزام به یک هدر سفارشی مانند X-CSRF-Token یک لایه‌ی دفاعی مؤثر است، چون مرورگر برای درخواست‌های Cross-Origin، هدرهای سفارشی را به‌صورت خودکار اضافه نمی‌کند و افزودن آن‌ها از یک دامنه‌ی دیگر، نیازمند CORS صریح است.

مقایسه الگوهای دفاعی

الگو مقاومت Stateless محدودیت اصلی
Synchronizer Token بسیار بالا خیر نیاز به ذخیره‌سازی سمت سرور
Double Submit متوسط بله آسیب‌پذیری در برابر زیردامنه‌ی مخرب
Signed Double Submit بالا بله پیچیدگی مدیریت کلید امضا
Origin Check پایین بله نبود هدر در برخی Contextها
Custom Header بالا (با CORS دقیق) بله نیازمند کنترل CORS سختگیرانه

تجربه‌ی عملی نشان می‌دهد که ترکیب Synchronizer Token با بررسی Origin و هدرهای امنیتی، بهترین توازن میان امنیت و پیچیدگی را ارائه می‌دهد. برای مطالعه‌ی بیشتر درباره‌ی هدرهای امنیتی و نقش آن‌ها در دفاع لایه‌ای، پست هدرهای امنیتی HTTP چه کاربردی دارند مرجع مناسبی است.

طراحی توکن ضد CSRF

یک توکن ضد CSRF مؤثر، تنها به تولید تصادفی مناسب نیاز ندارد؛ بلکه باید در چرخه‌ی عمر نشست به‌درستی مدیریت شود. چند اصل کلیدی در طراحی توکن وجود دارد:

  • آنتروپی کافی: حداقل ۱۲۸ بیت آنتروپی. تولید با random_bytes در PHP یا secrets در پایتون.
  • پیوند به نشست: توکن باید به‌صورت یکتا با نشست کاربر مرتبط باشد، نه به‌صورت سراسری.
  • عمر محدود: توکن‌هایی که برای همیشه ثابت می‌مانند، در صورت نشت، خطر بزرگی می‌سازند.
  • یک‌بارمصرف بودن در موارد حساس: برای عملیات حساس مانند تغییر رمز، توکن باید پس از مصرف باطل شود.
  • مقایسه‌ی زمان-ثابت: استفاده از hash_equals به‌جای == برای جلوگیری از حملات زمان‌سنجی.

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

CSRF در وردپرس و اکوسیستم آن

در اکوسیستم وردپرس، مکانیزم پیش‌فرض برای دفاع در برابر CSRF، استفاده از Nonce است. Nonce یک توکن یک‌بارمصرف است که با یک عمر مشخص (به‌طور پیش‌فرض ۱۲ تا ۲۴ ساعت) تولید می‌شود و به کاربر، عملیات و زمان وابسته است. توابع اصلی شامل wp_nonce_field، wp_create_nonce، wp_verify_nonce و check_admin_referer هستند.

// در فرم
wp_nonce_field("my_action", "my_nonce");

// در پردازش
if (!isset($_POST["my_nonce"]) || !wp_verify_nonce($_POST["my_nonce"], "my_action")) {
    wp_die("درخواست نامعتبر");
}

نکته‌ی مهم این است که Nonce وردپرس یک مکانیزم ضد CSRF است، نه یک مکانیزم احراز هویت. یعنی Nonce نمی‌گوید کاربر کیست؛ فقط می‌گوید درخواست از یک صفحه‌ی معتبر همان سایت آمده است. بنابراین، Nonce باید در کنار بررسی current_user_can استفاده شود، نه به‌جای آن.

محدودیت‌های Nonce در وردپرس را باید جدی گرفت:

  • Nonce در وردپرس به‌صورت سراسری توسط همه‌ی کاربران قابل استفاده است و به کاربر خاصی گره نخورده؛ اگرچه در تولید مقدار، شناسه کاربر و نشست نقش دارند.
  • Nonce در محیط‌های با Cache صفحه، ممکن است اشتباه عمل کند و منجر به خطاهای ناخواسته برای کاربران شود.
  • Nonce وردپرس در چندین درخواست قابل استفاده است و یک‌بارمصرف نیست.

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

Nonce در درخواست‌های AJAX وردپرس

در درخواست‌های AJAX، Nonce باید در payload یا هدر ارسال شود و در سمت سرور با check_ajax_referer بررسی گردد:

// ارسال
jQuery.post(ajaxurl, {
    action: "my_action",
    nonce: my_ajax_obj.nonce,
    data: payload
});

// دریافت
check_ajax_referer("my_action", "nonce");

در این الگو، اگر Nonce ارسال نشود یا نامعتبر باشد، پاسخ با کد 403 برگردانده می‌شود. این رفتار، مرز امنیتی بین درخواست معتبر و نامعتبر را در سطح کد روشن می‌کند.

CSRF در APIهای REST و JSON

APIهای REST که با احراز هویت مبتنی بر کوکی کار می‌کنند، دقیقاً همان آسیب‌پذیری CSRF را دارند. اگر API فقط با JWT در هدر Authorization محافظت شود، CSRF از اساس بی‌معنا می‌شود، چون مهاجم نمی‌تواند هدر Authorization را به‌صورت خودکار در درخواست Cross-Origin قرار دهد. اما اگر API به کوکی نشست تکیه کند، دفاع CSRF الزامی است.

در APIهای REST، دفاع CSRF معمولاً به یکی از این شکل‌ها پیاده‌سازی می‌شود:

  • بررسی هدر Origin و رد کردن درخواست‌های غیرمجاز.
  • الزام به هدر سفارشی مانند X-CSRF-Token و بررسی مقدار آن.
  • استفاده از توکن همگام برای درخواست‌های تغییردهنده‌ی حالت.
  • استفاده از SameSite=Strict برای کوکی نشست و جدا کردن مسیر API از مسیر UI.

برای مطالعه‌ی معماری امنیتی APIهای REST و نحوه‌ی پیاده‌سازی دقیق این الگوها، پست امنیت API در وب چگونه تامین می‌شود راهنمای عملی و کاملی است.

پرسش‌های پرتکرار در مورد محافظت از فرم‌ها در برابر CSRF

در این بخش، پرتکرارترین پرسش‌هایی که در پروژه‌ها و جلسات بازبینی امنیتی با آن‌ها مواجه می‌شویم، پاسخ داده می‌شود.

آیا SameSite=Lax کافی است؟

خیر. SameSite=Lax بسیاری از سناریوهای کلاسیک را می‌بندد، اما در سناریوهای SSO، زیردامنه‌های نامعتبر، مرورگرهای قدیمی و درخواست‌های GET با اثر جانبی، کافی نیست. SameSite یک لایه است، نه یک راه‌حل کامل.

آیا Nonce وردپرس جایگزین توکن همگام است؟

Nonce وردپرس یک نوع توکن همگام است، اما با محدودیت‌های خاص خود. برای عملیات حساس، ترکیب Nonce با یک توکن یک‌بارمصرف توصیه می‌شود.

آیا استفاده از HTTPS CSRF را رفع می‌کند؟

خیر. HTTPS فقط کانال انتقال را امن می‌کند و از شنود جلوگیری می‌کند. CSRF از یک مسیر قانونی، یعنی مرورگر خود کاربر، سوءاستفاده می‌کند و HTTPS مانع آن نمی‌شود.

آیا توکن CSRF باید در کوکی ذخیره شود یا در نشست؟

اگر معماری Stateless باشد، Double Submit با امضا مناسب است. اگر معماری Stateful باشد، Synchronizer Token در نشست، امن‌تر و ساده‌تر است.

آیا CSRF روی APIهایی که با JWT کار می‌کنند هم اثر دارد؟

اگر JWT در هدر Authorization باشد، خیر. اگر JWT در کوکی ذخیره شود، بله و همان دفاع‌های CSRF لازم است.

آیا باید برای همه‌ی فرم‌ها توکن CSRF گذاشت؟

برای همه‌ی فرم‌هایی که حالت سرور را تغییر می‌دهند (POST، PUT، DELETE)، بله. فرم‌های فقط-خواندنی نیازی ندارند، اما در عمل، افزودن توکن به همه‌ی فرم‌ها ساده‌تر از تصمیم‌گیری موردی است.

اشتباهات رایج در پیاده‌سازی دفاع CSRF

در بازبینی‌های امنیتی، الگوهای تکرارشونده‌ای از اشتباهات را می‌بینیم که هرکدام می‌توانند دفاع CSRF را بی‌اثر کنند. در ادامه، مهم‌ترین آن‌ها فهرست شده‌اند.

نشانه علت ریشه‌ای راه‌حل
توکن در URL قرار می‌گیرد نشت در Referer و لاگ‌ها توکن فقط در بدنه‌ی درخواست یا هدر سفارشی
توکن ثابت و بدون چرخه عدم باطل‌سازی پس از مصرف تولید مجدد پس از ورود و عملیات حساس
توکن از دامنه‌ی کاربر می‌آید اعتماد به ورودی کلاینت تولید توکن در سمت سرور و مقایسه‌ی زمان-ثابت
SameSite=None بدون Secure نادیده گرفتن الزامات مرورگر SameSite=None همواره با Secure
Origin بررسی نمی‌شود اعتماد کامل به توکن افزودن بررسی Origin به‌عنوان لایه‌ی دوم
Nonce وردپرس بدون بررسی دسترسی اشتباه گرفتن CSRF با Authorization ترکیب Nonce با current_user_can

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

تست و اعتبارسنجی دفاع CSRF

پس از پیاده‌سازی، باید اثربخشی دفاع تأیید شود. فرآیند تست CSRF از چند مرحله تشکیل می‌شود:

  1. شناسایی endpointهای تغییردهنده‌ی حالت: هر درخواستی که POST، PUT، PATCH یا DELETE دارد.
  2. بررسی وجود توکن: آیا فرم‌ها شامل فیلد Nonce یا توکن هستند؟
  3. حذف توکن و ارسال مجدد: آیا سرور درخواست را رد می‌کند؟
  4. جعل توکن با مقدار دلخواه: آیا سرور توکن را معتبر می‌داند؟
  5. بررسی SameSite روی کوکی نشست: آیا مقدار تنظیم شده و آیا مرورگر آن را اعمال می‌کند؟
  6. بررسی رفتار Cross-Origin: با یک صفحه‌ی تست، درخواست Cross-Origin ارسال و پاسخ سرور بررسی شود.

ابزارهایی مانند Burp Suite، OWASP ZAP و اسکریپت‌های سفارشی می‌توانند در این فرآیند کمک کنند. اما نکته‌ی کلیدی این است که تست باید در محیطی انجام شود که دقیقاً مشابه Production باشد، چون رفتار SameSite، CORS و Cookie Domain در محیط‌های مختلف تفاوت معناداری دارند.

لایه‌های مکمل امنیتی

دفاع CSRF تنها یکی از لایه‌های امنیتی است و بدون لایه‌های مکمل، اثربخشی آن محدود می‌شود. مهم‌ترین لایه‌های مکمل عبارتند از:

  • Content Security Policy (CSP): مانع اجرای اسکریپت‌های ناخواسته می‌شود و سناریوهای XSS-based CSRF را محدود می‌کند.
  • Secure و HttpOnly روی کوکی نشست: مانع دسترسی جاوااسکریپت به کوکی می‌شود.
  • بررسی ورودی و خروجی: برای جلوگیری از XSS که می‌تواند توکن را بدزدد.
  • مدیریت صحیح CORS: از اعتماد بی‌مورد به دامنه‌های خارجی جلوگیری می‌کند.
  • مانیتورینگ و لاگ‌گیری: برای شناسایی تلاش‌های ناموفق و الگوهای مشکوک.

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

ملاحظات معماری پیشرفته

در معماری‌های توزیع‌شده و میکروسرویس، دفاع CSRF پیچیدگی‌های خاص خود را پیدا می‌کند. چند نکته‌ی کلیدی که در پروژه‌های بزرگ با آن‌ها مواجه می‌شویم:

1. توکن متمرکز در لبه (Edge): در معماری‌هایی که از API Gateway یا Reverse Proxy استفاده می‌کنند، می‌توان اعتبارسنجی CSRF را در همان لایه انجام داد و از تکرار منطق در سرویس‌ها جلوگیری کرد. این الگو، کاهش سطح حمله و سادگی نگهداری را به همراه دارد.

2. مرزهای دامنه و زیردامنه: در سازمان‌هایی که از چندین زیردامنه استفاده می‌کنند، باید به‌صورت آگاهانه تصمیم گرفت که کدام دامنه‌ها هم‌سایت محسوب می‌شوند و کدام‌ها نه. یک زیردامنه‌ی قدیمی که فراموش شده، می‌تواند به پاشنه‌ی آشیل CSRF تبدیل شود.

3. مهاجرت از Stateful به Stateless: اگر در حال مهاجرت از نشست‌های کوکی به JWT هستید، توجه داشته باشید که این مهاجرت، سطح حمله CSRF را تغییر می‌دهد، نه صرفاً حذف می‌کند. اگر JWT در کوکی نگهداری شود، همان دفاع‌ها لازم است.

4. تفکیک مسیر API و UI: اگر مسیر API روی زیردامنه‌ی جداگانه باشد و کوکی نشست به‌درستی محدود شده باشد، می‌توان بخش بزرگی از سطح حمله CSRF را حذف کرد. این الگو در پروژه‌های بزرگ به‌عنوان یک تصمیم معماری ارزشمند تلقی می‌شود.

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

دفاع CSRF یک مسئله‌ی «تنظیم یک گزینه» نیست؛ یک تصمیم معماری است که از لایه‌ی مرورگر تا لایه‌ی ذخیره‌سازی امتداد دارد. هر جا این تصمیم به تعویق بیفتد، بدهی امنیتی انباشته می‌شود.

در انتها، باید پذیرفت که هیچ مکانیزمی به‌تنهایی کامل نیست. ترکیب توکن همگام، SameSite، بررسی Origin، هدرهای امنیتی و مانیتورینگ، یک دفاع لایه‌ای واقعی می‌سازد که هم در برابر حملات شناخته‌شده و هم در برابر حملات نوظهور، تاب‌آور است.

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

💡 نکته‌ی پایانی: دفاع CSRF را مانند یک لایه‌ی زنده ببینید، نه یک تنظیم ثابت. هر تغییر در معماری، هر افزودن زیردامنه، و هر مهاجرت احراز هویت، این لایه را دوباره به پرسش می‌کشد.