تأمین امنیت فرم‌ها با AntiForgeryToken در MVC: راهنمای جامع مقابله با حملات CSRF

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

تأمین امنیت فرم‌ها با AntiForgeryToken در MVC: راهنمای جامع مقابله با حملات CSRF

53 بازدید 0 نظر ۱۴۰۴/۰۷/۲۸

در اکوسیستم ASP.NET MVC، مایکروسافت یک راه‌حل قدرتمند، یکپارچه و ساده را برای مقابله با این تهدید فراهم کرده است: AntiForgeryToken. این مقاله به صورت عمیق به چیستی حمله CSRF، مکانیسم عملکرد AntiForgeryToken و نحوه پیاده‌سازی صحیح آن در برنامه‌های MVC می‌پردازد.

 

بخش اول: تهدید (The Threat) - CSRF چیست؟

برای درک ارزش AntiForgeryToken، ابتدا باید دقیقا بدانیم که با چه چیزی مبارزه می‌کنیم.

CSRF (خوانده می‌شود "see-surf") حمله‌ای است که کاربر احراز هویت شده را مجبور می‌کند تا یک درخواست ناخواسته را به یک وب‌سایت که در آن احراز هویت شده است، ارسال کند.

 

سناریوی یک حمله CSRF

بیایید یک سناریوی کلاسیک را تصور کنیم:

  1. ورود به سایت معتبر: شما وارد وب‌سایت بانک خود (BankMelli.com) می‌شوید. تیک "مرا به خاطر بسپار" را می‌زنید یا به هر حال، سِشِن (Session) شما فعال است. مرورگر شما یک کوکی احراز هویت (Authentication Cookie) برای دامنه BankMelli.com ذخیره می‌کند.

  2. بازدید از سایت مخرب: در حالی که تب بانک شما باز است (یا حتی اگر بسته باشد ولی کوکی معتبر باشد)، شما ایمیلی دریافت می‌کنید یا از یک فروم بازدید می‌کنید و روی یک لینک به ظاهر بی‌خطر کلیک می‌کنید که شما را به سایت Hacker.com می‌برد.

  3. عملیات مخفیانه: سایت Hacker.com حاوی یک کد HTML مخفی است. این کد می‌تواند یک تگ <img> باشد، یا یک فرم که به صورت خودکار با جاوااسکریپت ارسال (Submit) می‌شود.

برای مثال، کد موجود در Hacker.com می‌تواند چیزی شبیه به این باشد:

<html
  onload="document.forms[0].submit()"
>
  <body style="display:none;">
    <form
      action="https://BankMelli.com/Transfer/Execute"
      method="POST"
    >
      <input
        type="hidden"
        name="toAccount"
        value="123456789"
      />
      <input
        type="hidden"
        name="amount"
        value="10000000"
      />
      </form>
  </body>
</html>

 

چه اتفاقی می‌افتد؟

  • به محض بارگذاری صفحه Hacker.com، اسکریپت آن اجرا شده و فرم مخفی را به آدرس BankMelli.com/Transfer/Execute ارسال (POST) می‌کند.

  • نکته کلیدی: مرورگرها طوری طراحی شده‌اند که هنگام ارسال درخواست به یک دامنه (مثلاً BankMelli.com)، به طور خودکار تمام کوکی‌های مرتبط با آن دامنه (از جمله کوکی احراز هویت شما) را به همراه درخواست ارسال می‌کنند.

  • سرور BankMelli.com درخواست POST را دریافت می‌کند. به کوکی‌ها نگاه می‌کند و می‌بیند که شما (کاربر معتبر) احراز هویت شده‌اید. سرور هیچ راهی برای تشخیص اینکه این درخواست از جانب شما و با رضایت شما بوده یا از جانب یک سایت مخرب، ندارد.

  • در نتیجه، سرور درخواست را معتبر تلقی کرده و مبلغ ۱،۰۰۰،۰۰۰ تومان را به حساب هکر منتقل می‌کند.

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

 

بخش دوم: راه‌حل (The Solution) - AntiForgeryToken چگونه کار می‌کند؟

برای مقابله با CSRF، ما به چیزی بیش از کوکی نیاز داریم. ما به یک "مدرک" نیاز داریم که ثابت کند درخواست واقعاً از جانب فرمی است که خودِ سرور ما آن را به کاربر ارائه کرده است.

اینجاست که الگوی توکن همگام‌ساز (Synchronizer Token Pattern) وارد می‌شود، که AntiForgeryToken در MVC پیاده‌سازی دقیقی از آن است.

مکانیسم کار AntiForgeryToken بر اساس دو قطعه داده است که باید با هم مطابقت داشته باشند:

  1. یک کوکی (Cookie): هنگامی که کاربر برای اولین بار فرمی را درخواست می‌کند (یا هر صفحه‌ای که حاوی فراخوانی AntiForgeryToken باشد)، سرور یک کوکی HTTP-Only به نام __RequestVerificationToken (یا نام سفارشی دیگر) ایجاد می‌کند. این کوکی حاوی یک مقدار رمزنگاری شده و منحصربه‌فرد (توکن A) است.

  2. یک فیلد مخفی در فرم (Hidden Field): همزمان، سرور یک فیلد مخفی (<input type="hidden">) در داخل فرم HTML ایجاد می‌کند. این فیلد نیز حاوی یک مقدار رمزنگاری شده (توکن B) است.

نکته حیاتی: توکن A (در کوکی) و توکن B (در فرم) با هم مرتبط هستند و سرور می‌تواند این ارتباط را اعتبارسVنجی کند. (توکن B معمولاً حاوی همان اطلاعات توکن A به اضافه مقداری نمک (Salt) مانند نام کاربری کاربر احراز هویت شده است تا امنیت آن بیشتر شود).

 

فرآیند اعتبارسنجی

حالا ببینیم وقتی کاربر فرم را ارسال (Submit) می‌کند، چه اتفاقی می‌افتد:

  1. ارسال فرم: مرورگر فرم را ارسال می‌کند. این ارسال شامل تمام فیلدهای فرم، از جمله فیلد مخفی __RequestVerificationToken (توکن B) است.

  2. ارسال کوکی: همزمان، مرورگر طبق رفتار استاندارد خود، کوکی __RequestVerificationToken (توکن A) را نیز به همراه درخواست ارسال می‌کند.

  3. بررسی در سرور: سرور (به لطف فریمورک MVC) قبل از اجرای اکشن کنترلر، هر دو توکن را دریافت می‌کند:

    • توکن A از کوکی‌ها.

    • توکن B از بدنه فرم (Form Data).

  4. اعتبارسنجی: سرور این دو توکن را با هم مقایسه و اعتبارسنجی می‌کند.

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

    • اگر یکی از توکن‌ها موجود نباشد، یا با هم مطابقت نداشته باشند: سرور درخواست را نامعتبر دانسته، آن را رد می‌کند و یک خطای HttpAntiForgeryException پرتاب می‌کند.

 

چرا این روش حمله CSRF را خنثی می‌کند؟

به سناریوی Hacker.com برگردیم:

  • سایت Hacker.com می‌تواند فرم مخفی خود را به BankMelli.com ارسال کند.

  • مرورگر قربانی نیز کوکی __RequestVerificationToken (توکن A) را ارسال خواهد کرد.

  • اما، Hacker.com به دلیل سیاست مبدأ یکسان (Same-Origin Policy) مرورگرها، هیچ دسترسی به محتوای صفحه BankMelli.com ندارد. بنابراین، نمی‌تواند مقدار فیلد مخفی (توکن B) را بخواند و آن را در فرم مخرب خود کپی کند.

  • از آنجایی که Hacker.com نمی‌تواند توکن B صحیح را حدس بزند (چون رمزنگاری شده است)، فرم ارسالی آن فاقد توکن B معتبر خواهد بود.

  • در نتیجه، سرور BankMelli.com عدم تطابق را تشخیص داده و درخواست را مسدود می‌کند. حمله شکست می‌خورد.

 

بخش سوم: پیاده‌سازی (The Implementation) در ASP.NET MVC

زیبایی AntiForgeryToken در MVC، سادگی فوق‌العاده پیاده‌سازی آن است. این کار در دو مرحله انجام می‌شود:

مرحله ۱: افزودن توکن به فرم در View (Razor)

در فایل View (مثلاً Transfer.cshtml) که فرم شما در آن قرار دارد، کافی است از Helper متد @Html.AntiForgeryToken() در داخل تگ <form> استفاده کنید.

@model BankApp.Models.TransferViewModel

<h2>انتقال وجه</h2>

@using (Html.BeginForm("Execute", "Transfer", FormMethod.Post))
{
    @Html.AntiForgeryToken() <div class="form-group">
        @Html.LabelFor(m => m.ToAccount)
        @Html.TextBoxFor(m => m.ToAccount, new { @class = "form-control" })
        @Html.ValidationMessageFor(m => m.ToAccount)
    </div>
    
    <div class="form-group">
        @Html.LabelFor(m => m.Amount)
        @Html.TextBoxFor(m => m.Amount, new { @class = "form-control" })
        @Html.ValidationMessageFor(m => m.Amount)
    </div>
    
    <button type="submit" class="btn btn-primary">انتقال</button>
}

این کد دو کار انجام می‌دهد:

  1. اگر کوکی __RequestVerificationToken وجود نداشته باشد، آن را ایجاد می‌کند.

  2. یک فیلد مخفی در HTML رندر می‌کند:

    <input
      name="__RequestVerificationToken"
      type="hidden"
      value="یک_رشته_طولانی_و_رمزنگاری_شده..."
    />
    

 

مرحله ۲: اعتبارسنجی توکن در Controller (Action)

حالا باید به اکشن کنترلری که داده‌های این فرم را دریافت می‌کند (معمولاً اکشن [HttpPost]) بگوییم که توکن را اعتبارسنجی کند. این کار به سادگی با افزودن اتریبیوت (Attribute) [ValidateAntiForgeryToken] انجام می‌شود.

using System.Web.Mvc;

public class TransferController : Controller
{
    // این اکشن فرم را نمایش می‌دهد (GET)
    [HttpGet]
    public ActionResult Index()
    {
        var model = new TransferViewModel();
        return View(model);
    }

    // این اکشن داده‌های فرم را پردازش می‌کند (POST)
    [HttpPost]
    [ValidateAntiForgeryToken] // مهم: اعتبارسنجی اینجا انجام می‌شود
    public ActionResult Execute(TransferViewModel model)
    {
        if (ModelState.IsValid)
        {
            // ادامه منطق انتقال وجه...
            // چون درخواست از فیلتر [ValidateAntiForgeryToken] عبور کرده،
            // ما مطمئن هستیم که این یک درخواست CSRF نیست.
            
            _transferService.PerformTransfer(model.ToAccount, model.Amount);
            
            return RedirectToAction("Success");
        }
        
        // اگر مدل معتبر نبود، فرم را دوباره با خطاها نمایش بده
        return View("Index", model);
    }
}

تمام! به همین سادگی.

اتریبیوت [ValidateAntiForgeryToken] به طور خودکار قبل از اجرای کد متد Execute، به دنبال توکن در کوکی و توکن در داده‌های فرم می‌گردد، آن‌ها را مقایسه می‌کند و در صورت عدم تطابق، درخواست را با خطا مواجه می‌کند.

 

 

بخش چهارم: ملاحظات پیشرفته و AJAX

دنیای وب فقط فرم‌های HTML سنتی نیست. ما به طور گسترده از درخواست‌های AJAX (با استفاده از fetch یا jQuery) استفاده می‌کنیم. [ValidateAntiForgeryToken] چگونه با آن‌ها کار می‌کند؟

وقتی یک فرم را به صورت سنتی submit می‌کنید، تمام فیلدهای آن (از جمله فیلد مخفی) ارسال می‌شوند. اما در AJAX، شما باید داده‌ها را دستی ارسال کنید.

 

مشکل AJAX

اگر یک درخواست AJAX POST ارسال کنید، فیلد مخفی __RequestVerificationToken به طور خودکار همراه آن ارسال نمی‌شود. در نتیجه، اعتبارسنجی در سرور (که [ValidateAntiForgeryToken] دارد) شکست می‌خورد.

 

راه‌حل AJAX

خوشبختانه، اتریبیوت [ValidateAntiForgeryToken] فقط داده‌های فرم (Form Data) را بررسی نمی‌کند؛ بلکه هدرهای HTTP (HTTP Headers) را نیز بررسی می‌کند.

راه‌حل این است که مقدار توکن را از فیلد مخفی بخوانیم و آن را به عنوان یک هدر در درخواست AJAX خود قرار دهیم.

۱. دریافت توکن در جاوااسکریپت:

ابتدا باید مقدار توکن را از فیلد مخفی که @Html.AntiForgeryToken() ایجاد کرده است، بخوانیم.

// دریافت مقدار توکن با استفاده از jQuery
var token = $('input[name="__RequestVerificationToken"]').val();

// دریافت مقدار توکن با جاوااسکریپت خالص (Vanilla JS)
var token = document.querySelector(
  'input[name="__RequestVerificationToken"]'
).value;

۲. ارسال توکن در هدر درخواست AJAX:

حالا هنگام ارسال درخواست AJAX (مثلاً با jQuery $.ajax)، این توکن را در هدرها تنظیم می‌کنیم. نام هدر باید با نام فیلد مطابقت داشته باشد (یعنی __RequestVerificationToken).

// مثال با jQuery AJAX
var transferData = {
  toAccount: "987654321",
  amount: 5000,
};

$.ajax({
  url: "/Transfer/Execute",
  type: "POST",
  data: transferData,
  headers: {
    // مهم: افزودن توکن به هدرها
    __RequestVerificationToken: $(
      'input[name="__RequestVerificationToken"]'
    ).val(),
  },
  success: function (result) {
    alert("انتقال موفقیت‌آمیز بود!");
  },
  error: function (xhr, status, error) {
    // اگر توکن نامعتبر باشد، سرور خطای 400 یا 500 برمی‌گرداند
    alert("خطا در اعتبارسنجی یا پردازش.");
  },
});

سرور MVC (با اتریبیوت [ValidateAntiForgeryToken]) به طور هوشمندانه تشخیص می‌دهد که اگر توکن در بدنه فرم نبود، باید هدرها را برای یافتن آن بررسی کند. این مکانیسم، امنیت CSRF را برای APIها و درخواست‌های AJAX نیز فراهم می‌کند.

 

بخش پنجم: AntiForgeryToken در ASP.NET Core

ذکر این نکته خالی از لطف نیست که این مکانیسم در ASP.NET Core حتی ساده‌تر و هوشمندتر شده است:

  1. تزریق خودکار در فرم: اگر از Tag Helperها در Razor Pages یا Core MVC استفاده کنید (مثلاً <form asp-action="Execute" method="post">)، توکن ضد جعل به طور خودکار به تمام فرم‌هایی که method="post" دارند اضافه می‌شود. دیگر نیازی به @Html.AntiForgeryToken() دستی نیست.

  2. اعتبارسنجی خودکار: اتریبیوت جدیدی به نام [AutoValidateAntiforgeryToken] معرفی شده است. اگر این اتریبیوت را به صورت سراسری (Global) یا روی کنترلر اعمال کنید، فریمورک به طور خودکار اعتبارسنجی را برای تمام متدهای "ناامن" (POST, PUT, DELETE, PATCH) انجام می‌دهد و شما را از فراموش کردن [ValidateAntiForgeryToken] روی تک‌تک اکشن‌ها نجات می‌دهد.

 

نتیجه‌گیری

حمله CSRF یک تهدید واقعی و جدی برای هر برنامه‌ای است که به کاربران اجازه انجام عملیات حساس (تغییر رمز عبور، انتقال پول، حذف داده) را می‌دهد. این حمله از اعتماد مرورگر به ارسال خودکار کوکی‌های احراز هویت سوءاستفاده می‌کند.

فریمورک ASP.NET MVC با ارائه مکانیزم AntiForgeryToken، یک راه‌حل دفاعی لایه‌ای و قدرتمند ارائه می‌دهد. با استفاده از الگوی توکن همگام‌ساز (یک کوکی و یک فیلد مخفی منطبق)، این مکانیزم تضمین می‌کند که هر درخواست تغییر وضعیت (POST)، واقعاً از فرمی نشأت گرفته که توسط خود سرور ارائه شده است، نه از یک دامنه مخرب.

پیاده‌سازی آن با @Html.AntiForgeryToken() در View و اتریبیوت [ValidateAntiForgeryToken] در Controller به طرز شگفت‌آوری ساده است و نادیده گرفتن آن در توسعه فرم‌های حساس، یک سهل‌انگاری امنیتی بزرگ محسوب می‌شود.

 
لینک استاندارد شده: apA3YuCy1

0 نظر

    هنوز نظری برای این مقاله ثبت نشده است.
جستجوی مقاله و آموزش
دوره‌ها با تخفیفات ویژه