برای مثال، تصور کنید کاربر در یک بانک آنلاین لاگین کرده است. یک وبسایت مخرب میتواند با یک تصویر یا لینک، درخواست انتقال وجه به سرور بانک ارسال کند. چون مرورگر بهطور خودکار کوکی احراز هویت را همراه درخواست میفرستد، سرور بانک فکر میکند که خود کاربر درخواست را صادر کرده است.
برای مقابله با این حمله، فریمورک ASP.NET Core مکانیزمی به نام AntiForgery Token ارائه میدهد که در این مقاله بهطور کامل به بررسی آن میپردازیم.
AntiForgery Token یک رشته رمزنگاریشده است که توسط سرور تولید شده و در دو مکان ذخیره میشود:
وقتی کاربر فرمی را ارسال میکند، مرورگر بهطور خودکار کوکی را همراه درخواست میفرستد و مقدار فیلد مخفی نیز در دادههای فرم ارسال میشود. سرور سپس این دو مقدار را با یکدیگر مقایسه میکند. اگر با هم مطابقت داشته باشند، درخواست معتبر شناخته میشود؛ در غیر این صورت، درخواست با خطای 400 Bad Request رد میشود.
نکته کلیدی این است که کوکی AntiForgery HttpOnly نیست (یعنی قابل خواندن توسط جاوااسکریپت است)، اما مقدار آن بهصورت تصادفی و یکتا تولید میشود و مهاجم نمیتواند آن را پیشبینی کند. همچنین، این مکانیزم به هویت کاربر (Claims) گره خورده است، که در ادامه به تفصیل بررسی خواهیم کرد.
در ASP.NET Core، مدیریت AntiForgery توسط سرویس IAntiforgery انجام میشود که بهصورت پیشفرض در DI (Dependency Injection) ثبت شده است. برای استفاده از آن، دو مرحله اصلی وجود دارد:
۱. تولید توکن (در سمت کلاینت)
در Viewهای Razor، با کمک کمککننده (Helper) Html.AntiForgeryToken() یک فیلد مخفی تولید میشود:
html<form method="post">
@Html.AntiForgeryToken()
<!-- سایر فیلدها -->
<button type="submit">ارسال</button>
</form>
این کد معادل HTML زیر را تولید میکند:
html<input name="__RequestVerificationToken" type="hidden" value="CfDJ8N..." />
همزمان، یک کوکی با نام .AspNetCore.Antiforgery.xxxx در مرورگر تنظیم میشود.
۲. اعتبارسنجی (در سمت سرور)
برای اعتبارسنجی توکن دریافتی، از Attribute به نام ValidateAntiForgeryToken بر روی اکشنهای کنترلر استفاده میشود:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Product model)
{
// منطق برنامه
}
زمانی که درخواست به این اکشن میرسد، فریمورک بهطور خودکار کوکی و مقدار فیلد مخفی را بررسی میکند. اگر هرکدام از آنها وجود نداشته باشد یا با هم تطابق نداشته باشند، یک استثنا از نوع AntiforgeryValidationException پرتاب میشود و درخواست با Status Code 400 رد میشود.
ASP.NET Core چندین Attribute برای کنترل رفتار AntiForgery ارائه میدهد که هرکدام کاربرد خاصی دارند:
۱. ValidateAntiForgeryToken
این Attribute رایجترین و مهمترین ویژگی است. آن را بر روی اکشنهای HttpPost، HttpPut، HttpDelete و هر متدی که دادههای حساس را تغییر میدهد، قرار میدهیم. اگر درخواست فاقد توکن معتبر باشد، با خطا مواجه میشود.
نکته: استفاده از این Attribute بهصورت تکی برای هر اکشن، نیاز به دقت دارد و ممکن است فراموش شود. برای رفع این مشکل، گزینههای بعدی مطرح میشوند.
۲. AutoValidateAntiforgeryToken
این Attribute را میتوان بر روی کل کنترلر یا حتی بهصورت سراسری (Global) اعمال کرد تا تمام اکشنهای HttpPost بهطور خودکار دارای اعتبارسنجی AntiForgery باشند. بهعنوان مثال:
[AutoValidateAntiforgeryToken]
public class AccountController : Controller
{
// تمام متدهای POST این کنترلر بهطور خودکار اعتبارسنجی میشوند
}
یا در Program.cs (یا Startup.ConfigureServices) بهصورت سراسری:
services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
این کار، امنیت را افزایش میدهد و از فراموشی استفاده از ValidateAntiForgeryToken جلوگیری میکند.
۳. IgnoreAntiforgeryToken
اگر از AutoValidateAntiforgeryToken بهصورت سراسری استفاده میکنید، اما برخی اکشنها نیاز به عدم اعتبارسنجی دارند (مثلاً Webhookها یا APIهایی که از خارج سیستم فراخوانی میشوند)، میتوانید از این Attribute برای نادیدهگرفتن اعتبارسنجی استفاده کنید:
[HttpPost]
[IgnoreAntiforgeryToken]
public IActionResult Webhook([FromBody] SomeData data)
{
// این اکشن نیازی به توکن AntiForgery ندارد
}
یکی از مهمترین مفاهیم در AntiForgery، وابستگی به هویت کاربر است. توکن تولید شده نهتنها یک مقدار تصادفی است، بلکه شامل اطلاعاتی از Claims کاربر فعلی (بهویژه NameIdentifier یا Name) نیز میشود. این کار باعث میشود توکن برای کاربر خاصی معتبر باشد و قابل استفاده برای کاربر دیگر نباشد.
چرا این ارتباط مهم است؟
فرض کنید یک کاربر (الف) در سیستم لاگین کرده و یک فرم را باز کرده است (توکن برای او تولید شده). حالا یک کاربر دیگر (ب) که در سیستم لاگین دیگری دارد، سعی کند درخواستی را با استفاده از توکن کاربر (الف) ارسال کند. چون توکن به Claims کاربر (الف) گره خورده، سرور تشخیص میدهد که توکن متعلق به کاربر فعلی (ب) نیست و درخواست را رد میکند.
بهطور دقیقتر، هنگام تولید توکن، سرور اطلاعات زیر را درون توکن رمزگذاری میکند:
هنگام اعتبارسنجی، سرور توکن را رمزگشایی کرده و شناسه کاربری موجود در آن را با شناسه کاربری فعلی (که از کوکی احراز هویت یا JWT استخراج شده) مقایسه میکند. اگر این دو با هم برابر نباشند، خطای معروف زیر رخ میدهد:
The provided antiforgery token was meant for a different claims-based user than the current user.
سناریوی رایج: مشکل در حالت احراز هویت نشده در مقابل احراز هویت شده
یک مشکل بسیار رایج که بسیاری از توسعهدهندگان با آن مواجه میشوند، زمانی است که توکن در صفحهای تولید میشود که کاربر هنوز لاگین نکرده است (مانند صفحه ورود)، اما بعد از لاگین، از همان توکن برای درخواستهای بعدی استفاده میشود. از آنجا که توکن برای کاربر ناشناس (Anonymous) تولید شده، اما درخواست جدید با کاربر احراز هویت شده ارسال میشود، سرور خطای different claims-based user را برمیگرداند.
۱. انتخاب اشتباه توکن در DOM (علت اصلی خطای different user)
همانطور که در سوال اولیه مطرح شد، اگر در صفحه، چندین فیلد مخفی __RequestVerificationToken وجود داشته باشد (مثلاً یکی در Layout و دیگری در View جاری)، جاوااسکریپت ممکن است فیلد اشتباه را انتخاب کند. معمولاً فیلدی که در Layout قرار دارد، قبل از احراز هویت تولید شده است و متعلق به کاربر ناشناس است. وقتی این فیلد را برای درخواست AJAX استفاده میکنید، سرور خطای mismatch میدهد.
راهحل: به فرم خود یک id منحصربهفرد بدهید و توکن را دقیقاً از داخل آن فرم انتخاب کنید:
<form id="profileForm">
@Html.AntiForgeryToken()
<!-- سایر فیلدها -->
</form>
javascriptconst token = $('#profileForm input[name="__RequestVerificationToken"]').val();
یا اگر نمیتوانید به فرم id بدهید، همیشه آخرین توکن موجود در صفحه را انتخاب کنید (چون معمولاً آخرین توکن، مربوط به View جاری است):
const allTokens = document.querySelectorAll('input[name="__RequestVerificationToken"]');
const token = allTokens[allTokens.length - 1].value;
۲. کش شدن صفحه (Caching) و توکن قدیمی
اگر صفحه Profile بهصورت کش (Cache) در مرورگر یا سرور ذخیره شود، ممکن است توکن موجود در آن برای کاربر قبلی (یا حتی ناشناس) باشد و پس از لاگین، کاربر جدید از همان توکن استفاده کند. این نیز منجر به خطای mismatch میشود.
راهحل: از کش شدن صفحات حاوی فرمهای حساس جلوگیری کنید. در کنترلر، از Attribute زیر استفاده کنید:
ResponseCache(NoStore = true, Duration = 0)]
public IActionResult Profile()
{
return View();
}
همچنین، پس از لاگین، حتماً صفحه را با window.location.reload(true) یا window.location.href بهروزرسانی کنید تا توکن جدیدی از سرور دریافت شود.
۳. ارسال توکن در هدر (Header) به جای بدنه (Body)
بهطور پیشفرض، ValidateAntiForgeryToken توکن را از بدنه درخواست (بهعنوان یک فیلد فرم) دریافت میکند. اگر درخواستهای AJAX خود را با contentType: applicationjson ارسال میکنید، نمیتوانید توکن را در بدنه قرار دهید؛ بنابراین باید آن را در هدر ارسال کنید. برای این کار، باید تنظیمات AntiForgery را تغییر دهید:
services.AddAntiforgery(options =>
{
options.HeaderName = "X-CSRF-TOKEN";
});
سپس در جاوااسکریپت، هدر را به این صورت تنظیم کنید:
$.ajax({
url: '/auth/GetCheckPhoneEmailExistence',
type: 'POST',
headers: {
'X-CSRF-TOKEN': antiForgeryToken
},
// ...
});
نکته: اگر از پروکسی مانند NGINX استفاده میکنید، ممکن است هدرهایی که شامل زیرخط (_) هستند را حذف کند. بنابراین بهتر است از خط تیره (-) استفاده کنید، مانند X-CSRF-TOKEN.
۴. عدم ارسال کوکی AntiForgery بهدلیل تنظیمات Path یا Domain
کوکی AntiForgery بهصورت پیشفرض برای مسیر فعلی (Path) و دامنه (Domain) تنظیم میشود. اگر درخواست AJAX به مسیر دیگری (مثلاً api...) یا زیردامنه دیگری ارسال شود، ممکن است کوکی همراه درخواست نرود و اعتبارسنجی با خطا مواجه شود.
راهحل: در تنظیمات AntiForgery، مسیر کوکی را به ریشه () تنظیم کنید:
Services.AddAntiforgery(options =>
{
options.Cookie.Path = "/";
});
همچنین، اگر از چندین زیردامنه استفاده میکنید، Domain را بهصورت صریح تعیین کنید:
options.Cookie.Domain = ".yourdomain.com"; // نقطه ابتدایی برای همه زیردامنهها
۵. احراز هویت سفارشی (Custom Authentication) و AntiForgery
همانطور که در پاسخهای قبلی اشاره شد، AntiForgery با هر مکانیزم احراز هویتی که HttpContext.User را بهدرستی پر کند، کار میکند. چه از Identity پیشفرض استفاده کنید، چه JWT، چه کوکی سفارشی، تفاوتی ندارد. تنها نکته این است که ClaimsPrincipal باید شامل یک شناسه یکتا برای کاربر باشد (معمولاً NameIdentifier). اگر این Claim وجود نداشته باشد، AntiForgery نمیتواند توکن را به کاربر خاصی گره بزند و ممکن است رفتار غیرمنتظرهای داشته باشد.
راهحل: اطمینان حاصل کنید که پس از احراز هویت، حداقل Claim زیر به HttpContext.User اضافه شده است:
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
new Claim(ClaimTypes.Name, userName),
// ...
};
۶. تست با ابزارهای API (مانند Postman)
هنگام تست اکشنهای محافظتشده با ValidateAntiForgeryToken از طریق ابزارهایی مانند Postman یا Swagger، باید توکن و کوکی را بهصورت دستی استخراج و ارسال کنید. برای سادهتر کردن تست، میتوانید در محیط توسعه (Development) از شرط زیر استفاده کنید:
if (Environment.IsDevelopment())
{
// از اعتبارسنجی صرفنظر کن یا توکن را خودکار تولید کن
}
اما این کار را برای محیط Production هرگز انجام ندهید.
علاوه بر موارد ذکرشده، میتوانید تنظیمات بیشتری را برای AntiForgery سفارشی کنید:
مثال کامل از تنظیمات در Program.cs (.NET 6):
builder.Services.AddAntiforgery(options =>
{
options.HeaderName = "X-CSRF-TOKEN";
options.Cookie.Name = ".AspNetCore.Antiforgery";
options.Cookie.HttpOnly = false; // برای خواندن توسط جاوااسکریپت (اختیاری)
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.FormFieldName = "__RequestVerificationToken";
});
علاوه بر AntiForgery توکن، روشهای دیگری نیز برای مقابله با CSRF وجود دارند:
در ASP.NET Core، ترکیب AntiForgery Token با SameSite Cookies بهترین رویه امنیتی محسوب میشود.
۱. همیشه از ValidateAntiForgeryToken یا AutoValidateAntiforgeryToken برای متدهای POST، PUT، DELETE استفاده کنید.
۲. از IgnoreAntiforgeryToken تنها در صورت ضرورت و با دقت بالا استفاده کنید.
۳. توکن را در هدر ارسال کنید اگر درخواستهای AJAX شما با JSON کار میکنند.
۴. از کش شدن صفحات حاوی فرمها جلوگیری کنید تا توکنهای قدیمی باعث بروز خطا نشوند.
۵. در انتخاب توکن در DOM دقت کنید و همیشه توکن مربوط به فرم جاری را انتخاب کنید.
۶. تنظیمات کوکی را بررسی کنید تا مسیر و دامنه بهدرستی تنظیم شده باشند.
۷. در محیط توسعه، لاگهای دقیق را فعال کنید تا در صورت بروز خطا، دلیل آن را بهسرعت پیدا کنید. برای این کار، لاگر را در Program.cs تنظیم کنید:
builder.Logging.AddConsole();
builder.Logging.AddDebug();
سپس خطاهای AntiForgery در خروجی کنسول نمایش داده میشوند.
AntiForgery Token یکی از ارکان امنیت در برنامههای وب ASP.NET Core است که بهطور مؤثر از حملات CSRF جلوگیری میکند. این مکانیزم با استفاده از یک کوکی و یک فیلد مخفی، درخواستهای ورودی را اعتبارسنجی میکند و با گرهزدن توکن به هویت کاربر (Claims-based)، از سوءاستفاده توسط کاربران دیگر یا مهاجمان جلوگیری مینماید.
درک صحیح از نحوه تولید، ذخیرهسازی و اعتبارسنجی این توکن، به توسعهدهنده کمک میکند تا از بروز خطاهای رایج مانند different claims-based user جلوگیری کند. همچنین آشنایی با Attributeهای مختلف مانند ValidateAntiForgeryToken، AutoValidateAntiforgeryToken و IgnoreAntiforgeryToken به توسعهدهنده امکان میدهد تا امنیت را بهصورت سراسری یا جزئی اعمال کند.
در نهایت، با رعایت بهترینهای عملی و تنظیمات مناسب، میتوانید از دادههای کاربران خود در برابر حملات CSRF محافظت کنید و تجربهای امن برای آنها فراهم آورید. همیشه بهیاد داشته باشید که امنیت، یک فرایند مستمر است و نیاز به بررسی و بهروزرسانی مداوم دارد.
0 نظر
هنوز نظری برای این مقاله ثبت نشده است.