جلوگیری از حمله CSRF در درخواست Ajax با Asp.Net Core 3.1

CSRF عملی برای فریب دادن کاربر به درخواست وبسایتی است که قبلا در آن وارد سیستم شده است .

در مقاله اعتبارسنجی خودکار Anti-forgery tokens در Asp.Net Core 3.1 این مورد را بررسی کردیم و در اینجا میخواهیم برای درخواست های Ajax این کار را انجام دهیم .

برای جلوگیری از این سناریو باید موارد اضافی SameSite کوکی را به صورت زیر در web.config خود اضافه کنید :

<system.webServer>
  <rewrite>
    <outboundRules>
      <clear />
      <rule name="Add SameSite" preCondition="No SameSite">
        <match serverVariable="RESPONSE_Set_Cookie" pattern=".*" negate="false" />
        <action type="Rewrite" value="{R:0}; SameSite=lax" />
      </rule>
      <preConditions>
        <preCondition name="No SameSite">
          <add input="{RESPONSE_Set_Cookie}" pattern="." />
          <add input="{RESPONSE_Set_Cookie}" pattern="; SameSite=lax" negate="true" />
        </preCondition>
      </preConditions>
    </outboundRules>
  </rewrite>
  ...
</system.webServer>

و همچنین باید یک CSRF Token را تولید و آن را برای اعتبارسنجی در کلیه درخواست های POST ، PUT و DELETE به سرور ارسال کنید .

Asp.Net Core به طور خودکار یک CSRF Token پنهان را در تمام عناصر فرم بدون Action filter تزریق میکند

هنگامی که شما یک Single page application دارید پست ها به صورت Ajax ساخته می شوند برای درخواست های Ajax که از طریق جاوا اسکریپت ارسال می شوند باید  CSRF Token را خودتان تهیه کنید در اینجا من از Jquery استفاده میکنم .

برای تست قطعه کد طریق جاوا اسکریپت زیر را در نظر بگیرید :

$(document).ready(function () {
    $.post("/home/post");
});

و همچنین کنترلر آن را هم میسازیم :

[HttpPost]
public IActionResult Post()
{
    return Ok();
}

همانطور که در Console مرورگر مشاهده میکنید Asp.Net Core نمیتواند یک نشانه معتبر را پیدا کند و وضعیت 400 را برمیگرداند .

برای اضافه کردن CSRF Token یا بهتره بگیم Anti-forgery tokesn کد زیر را قبل از تگ <body/> قرار میدهیم :

()Html.AntiForgeryToken@

 با این کار یک عنصر پنهان <input> با CSRF Token ایجاد می شود :

<input name="__RequestVerificationToken" type="hidden" value="CfDJ8D3coQ0eo-9FihbOkfTdm5K4iaWDlp1yE3ciyFjA7FfCVlfEyyNLsu8Py50neE2yWPB0r8pdiV1FjRj-I7NgUbX2aqZdz0enZvIY5utbLGKZjsrfzqvPEf-lsswKTC4gmcgElt1pg67VryXWrE8gV6o" />

برای ارسال این Token ایجاد شده با هر درخواست Ajax کد زیر را وارد کنید :

var e = $('input[name="__RequestVerificationToken"]').val();
$(document)
    .ajaxSend(function (t, a, i) {
        a && i && ("POST" === i.type || "PUT" === i.type || "DELETE" === i.type) && a.setRequestHeader("RequestVerificationToken", e);
    });

توجه کنید که چگونه عنصر <input> دارای پیشوند __ است در حالی که چنین هدری وجود ندارد

در صورت تمایل میتوانید با وارد کردن پیکربندی زیر در Startup اسم هدر را بازنویسی کنید :

services.AddAntiforgery(options =>
{
    options.HeaderName = "__RequestVerificationToken";
});

و حالا برای کلیه درخواست های POST ، PUT و DELETE که از طریق Jquery انجام می شود ما میتوانیم هدر RequestVerificationToken را را با مقدار <input> که قبلا تولید شده است ارسال کنیم .

در آخر اگر شما یک یا چند کنترلر دارید که نمیخواهید این اعتبارسنجی انجام شود اینکار را میتوانید با Attribute زیر انجام دهید :

[IgnoreAntiforgeryToken]
public class SpecialController : Controller
{
}