عنوان:

‫تحول در امنیت ASP.NET Core 11: بررسی عمیق مکانیسم خودکار و بدون پیکربندی مقابله با حملات CSRF


نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۴/۳۱ ۰۹:۴۵
آدرس: www.dntips.ir
چکیده
حمله جعل درخواست در سراسر سایت (Cross-Site Request Forgery - CSRF) یکی از آسیب‌پذیری‌های کلاسیک و در عین حال خطرناک در وب به شمار می‌رود. فریم‌ورک ASP.NET Core سال‌ها برای مقابله با این حمله از روش مبتنی بر توکن (Token-based Antiforgery) استفاده می‌کرد. با این حال، نیاز به تزریق توکن در فرم‌ها، مدیریت کوکی‌های مربوطه و پیکربندی دستی میدل‌ورها در الگوی‌های مختلف نظیر Blazor SSR، MVC و Minimal APIs همواره چالش‌هایی را برای توسعه‌دهندگان به همراه داشته است.
در نسخه NET 11 (Preview 6).، تیم ASP.NET Core یک قابلیت جدید تحت عنوان «محافظت متقاطع خودکار بر مبنای هدرهای مدرن مرورگر» معرفی کرده است. این ویژگی به‌صورت پیش‌فرض (Zero-Configuration) بر روی برنامه‌های ساخته‌شده با WebApplication.CreateBuilder() فعال بوده و بدون نیاز به مدیریت توکن، درخواست‌های ناامن و مشکوک Cross-Origin را مسدود می‌سازد. در این مقاله به بررسی این معماری، الگوریتم تصمیم‌گیری، ارزیابی تنبل (Lazy Evaluation)، نحوه یکپارچه‌سازی با سیستم‌های موجود و نکات زیرساختی این قابلیت می‌پردازیم.


مقدمه و مسئله CSRF در وب مدرن
در سناریوهای سنتی، مرورگرها به همراه هر درخواست HTTP به یک دامنه target، تمامی کوکی‌های معتبر آن دامنه (از جمله کوکی نشست یا Authentication Cookie) را ارسال می‌کردند. اگر کاربری در یک سایت مخرب حضور می‌داشت، آن سایت می‌توانست درخواستی به صورت مخفیانه به دامنه مورد نظر ارسال کرده و از اعتبارات کاربر سوءاستفاده کند.
برای سال‌ها، راهکار اصلی فریم‌ورک‌ها استفاده از توکن‌های ضد جعل (Antiforgery Tokens) بود. این روش اگرچه کارآمد است، اما با دو مشکل عمده روبرو است:
  • ارتباط با حالت (Statefulness): تولید و اعتبارسنجی توکن نیازمند صرف منابع پردازشی و گاه ذخیره‌سازی در نودهای سرور است.
  • بار پیچیدگی توسعه: توسعه‌دهنده باید مطمئن می‌شد که میدل‌ور app.UseAntiforgery() فراخوانی شده و تمامی فرم‌ها شامل توکن مربوطه هستند.
با ورود استاندارد W3C Fetch Metadata به مرورگرهای مدرن، هدرهای امنیتی جدیدی نظیر Sec-Fetch-Site و Origin معرفی شدند. فریم‌ورک ASP.NET Core 11 از این هدرها بهره می‌جوید تا نوع منبع درخواست را احراز کند؛ بدون اینکه نیازی به بررسی توکن در تمام لایه‌ها باشد.

الگوریتم ارزیابی و تصمیم‌گیری درخواست‌ها
ویژگی جدید CrossOriginProtection بر پایه بررسی هدرهای استاندارد HTTP عمل می‌کند. الگوریتم تصمیم‌گیری میدل‌ور جدید برای تعیین مجاز (Allowed) یا غیرمجاز (Denied) بودن درخواست به شرح زیر است:
[ ورود درخواست HTTP ]
         │
         ▼
آیا متد امن است؟ (GET, HEAD, OPTIONS, TRACE) ─────────► [ مجاز / Allowed ]
         │ خیر
         ▼
آیا مبدأ در TrustedOrigins (خط‌مشی CORS) است؟ ─────────► [ مجاز / Allowed ]
         │ خیر
         ▼
آیا هدر Sec-Fetch-Site موجود است؟
         ├─► مقادیر "same-origin" یا "none" ───────────► [ مجاز / Allowed ]
         └─► مقادیر دیگر ("cross-site", "same-site") ──► [ غیرمجاز / Denied ]
         │ (موجود نیست)
         ▼
آیا هدر Origin موجود است؟
         ├─► مطابقت با Host درخواست ──────────────────► [ مجاز / Allowed ]
         └─► عدم مطابقت با Host درخواست ──────────────► [ غیرمجاز / Denied ]
         │ (موجود نیست)
         ▼
درخواست بدون هدر مرورگر (مانند cURL, Postman) ───────► [ مجاز / Allowed ]

جزییات تکمیلی الگوریتم:
  • متدهای امن (Safe HTTP Methods): متدهای GET، HEAD، OPTIONS و TRACE وضعیت سیستم را تغییر نمی‌دهند؛ بنابراین همواره مجاز تلقی می‌شوند.
  • یکپارچگی با CORS: مبادلات Cross-Origin معتبری که در سیاست‌های پیش‌فرض CORS (بخش AddCors) ثبت شده‌اند (مثلاً policy.WithOrigins("[https://sso.example.com](https://sso.example.com)")) به‌صورت خودکار مجاز شناخته می‌شوند.
نکته امنیتی: سیاست‌هایی که از AllowAnyOrigin() استفاده می‌کنند نادیده گرفته می‌شوند، زیرا اعتماد به تمام دامنه‌ها هدف اصلی مقابله با CSRF را نقض می‌کند.
  • درخواست‌های غیر مرورگری: کلاینت‌های غیر مرورگری (مانند سرویس‌های gRPC، ابزارهای cURL یا ارتباطات Server-to-Server) که هدرهای Sec-Fetch-Site یا Origin را ارسال نمی‌کنند، متوقف نشده و مجاز دانسته می‌شوند.

معماری داخلی و الگوی «ارزیابی تنبل» (Lazy Evaluation)
یکی از درخشان‌ترین نکات معماری در ASP.NET Core 11، تغییر رفتار میان‌افزار CsrfProtectionMiddleware است. این میان‌افزار درخواست‌های Cross-Origin نامعتبر را بلافاصله قطع (Short-Circuit) نمی‌کند.

ثبت حکم درIAntiforgeryValidationFeature
میدل‌ور CSRF پس از بررسی هدرها، نتیجه ارزیابی خود (IsValid = true یا IsValid = false) را روی رابط IAntiforgeryValidationFeature در HttpContext ثبت کرده و اجازه می‌دهد درخواست به سمت لایه‌های پایین‌تر (Downstream) حرکت کند.
// نمایی مفهومی از ثبت حکم در Context
httpContext.Features.Set<IAntiforgeryValidationFeature>(new AntiforgeryValidationFeature
{
    IsValid = isAllowed,
    // ...
});

چرا ارزیابی تنبل (Lazy Evaluation) اهمیت دارد؟
اگر درخواستی حکم IsValid == false دریافت کند، هیچ اتفاق بالادستی رخ نمی‌دهد؛ مگر زمانی که یک کامپوننت یا Endpoint تصمیم به خوانش بدنه فرم (Form Consumption) بگیرد.
  • سناریوی اول - Endpoint از فرم استفاده می‌کند: (مانند اکشن MVC دارای Antiforgery، اکشن Minimal API با مشخصه [FromForm]، یا ارسال فرم در Blazor SSR). در این حالت، مصرف‌کننده ویژگی IAntiforgeryValidationFeature را بررسی کرده، متوجه حکم IsValid == false شده و درخواست را با خطای HTTP 400 Bad Request رد می‌کند.
  • سناریوی دوم - Endpoint کاری با فرم ندارد: اگر درخواست به یک مسیر ارسال شود که اصلاً بدنه فرم را بازخوانی نمی‌کند، حکم صادر شده نادیده گرفته شده و درخواست روند طبیعی خود را طی می‌کند.

مصرف‌کنندگان چهارگانه حکم CSRF
در فریم‌ورک ASP.NET Core، چهار بخش اصلی وجود دارند که ویژگی IAntiforgeryValidationFeature را بازخوانی کرده و در صورت نادرست بودن حکم، برگرداندن پاسخ ۴۰۰ را به عهده می‌گیرند:
  • AntiforgeryMiddlewareAuthorizationFilter (معماری MVC / Razor Pages)
  • RequestDelegateFactory (در Minimal APIs هنگام بایندینگ متغیرهای [FromForm])
  • FormFeature (به عنوان لایه پشتیبان هنگام بازخوانی مستقیم HttpContext.Request.Form)
  • RazorComponentEndpointInvoker (پردازشگر فرم‌ها در Blazor SSR)

تعامل با سیستم توکن (Token-Based Antiforgery) و تغییرات ناسازگار با نگارش‌های قبلی (Breaking Changes)
یکی از پرسش‌های مهم این است که اگر برنامه همزمان از توکن ضد جعل و این قابلیت جدید استفاده کند، چه اولویتی برقرار خواهد بود؟
تقدم اعتبارسنجی توکن بر CSRF
در صورتی که در برنامه خود فراخوانی app.UseAntiforgery() را داشته باشید، میدل‌ور توکن پس از میدل‌ور CSRF اجرا می‌شود. میدل‌ور توکن به عنوان مرجع نهایی (Authoritative) عمل کرده، حکم قبلی CSRF را پاک می‌کند و نتیجه اعتبارسنجی توکن را جایگزین می‌نماید.
  • اگر درخواست توسط CSRF رد شده باشد اما توکن آن معتبر باشد، حکم نهایی IsValid = true خواهد بود.
  • و برعکس، اگر CSRF درخواست را مجاز بداند اما توکن نامعتبر باشد، حکم نهایی IsValid = false خواهد شد.

تغییر رفتار در Razor Components (Blazor SSR)
در نسخه‌های قبلی، RazorComponentEndpointInvoker مستقیماً متد IAntiforgery.ValidateRequestAsync را فراخوانی می‌کرد. در .NET 11:
  • این کلاس دیگر اعتبارسنجی را مستقیماً انجام نمی‌دهد، بلکه به حکم به‌جای‌مانده در IAntiforgeryValidationFeature اعتماد می‌کند.
  • در برنامه‌های Blazor Web App، دیگر نیازی به فراخوانی دستی app.UseAntiforgery() وجود ندارد، زیرا میدل‌ور CSRF به‌صورت خودکار تزریق شده و محافظت لایه اول را فراهم می‌سازد.

بررسی زیرساخت، APIهای عمومی و چالش‌های کامپایل JIT
رابط‌های عمومی (Public APIs)
قابلیت جدید دو نوع عمومی در فضای نام Microsoft.AspNetCore.Antiforgery معرفی می‌کند:
namespace Microsoft.AspNetCore.Antiforgery;

public interface ICsrfProtection
{
    CsrfProtectionResult Validate(HttpContext httpContext);
}

public enum CsrfProtectionResult
{
    Allowed,
    Denied
}

محل قرارگیریICsrfProtectionو مسئله JIT Compiler
در مراحل طراحی اولیه، قرار بود ICsrfProtection درون اسمبلی Microsoft.AspNetCore.Antiforgery.dll باقی بماند. اما در تست‌های یکپارچه‌سازی مشکلی کشف شد:
۱. کلاس ConfigureWebDefaultsWorker در فرایند ساخت برنامه، متد زیر را اجرا می‌کرد:
services.TryAddSingleton<ICsrfProtection>(...);
۲. کامپایلر JIT دات‌نت هنگام بارگذاری کدهای ConfigureWebDefaultsWorker (که در تمامی پروژه‌های مبتنی بر WebApplication.CreateBuilder() اجرا می‌شود)، مجبور به بارگذاری اسمبلی حاوی پارامتر ژنریک ICsrfProtection بود.
۳. پروژه‌های سبک‌تر یا تست‌هایی نظیر OpenAPI، Validation و StaticAssets که نیازی به اسمبلی Antiforgery نداشتند، به دلیل عدم وجود Microsoft.AspNetCore.Antiforgery.dll در خروجی bin خود با خطا مواجه می‌شدند.
راهکار: تیم دات‌نت رابط ICsrfProtection را به اسمبلی پایه Microsoft.AspNetCore.Http.Abstractions منتقل کرد تا بدون ایجاد وابستگی‌های اضافی به سایر اسمبلی‌ها، در تمامی پروژه‌ها قابل استفاده باشد.

نمونه کدهای کاربردی
۱. ثبت پیاده‌سازی سفارشی برایICsrfProtection
اگر نیاز به منطق سفارشی برای احراز صحت درخواست‌های Cross-Origin دارید، می‌توانید پیاده‌سازی خود را در DI جایگزین کنید:
using Microsoft.AspNetCore.Antiforgery;

var builder = WebApplication.CreateBuilder(args);

// ثبت پیاده‌سازی سفارشی (توجه: TryAddSingleton در اینجا پاسخگو نیست)
builder.Services.AddSingleton<ICsrfProtection, CustomCsrfProtection>();

var app = builder.Build();

app.MapPost("/api/data", ([FromForm] MyModel data) => Results.Ok(data));

app.Run();

public class CustomCsrfProtection : ICsrfProtection
{
    public CsrfProtectionResult Validate(HttpContext httpContext)
    {
        // منطق سفارشی بررسی هدرها یا آدرس‌ها
        var customHeader = httpContext.Request.Headers["X-Custom-Source"];
        if (customHeader == "TrustedApp")
        {
            return CsrfProtectionResult.Allowed;
        }

        return CsrfProtectionResult.Denied;
    }
}

۲. استثنا کردن یک Endpoint خاص (Opt-out)
در صورت نیاز به استثنا کردن یک مسیر (مانند Webhookها که از دامنه‌های خارجی ارسال می‌شوند)، می‌توانید از متد .DisableAntiforgery() یا ویژگی [IgnoreAntiforgeryToken] استفاده کنید:
// در Minimal APIs
app.MapPost("/api/v1/webhook", () => Results.Ok())
   .DisableAntiforgery();

// در MVC Controllers
[HttpPost]
[IgnoreAntiforgeryToken]
public IActionResult HandleExternalWebhook()
{
    return Ok();
}

۳. غیرفعال‌سازی سراسری (Global Disable)
در صورتی که قصد دارید این ویژگی را به طور کامل در سطح برنامه غیرفعال کنید، سه روش در دسترس است:
روش اول - فایل appsettings.json:
{
  "CrossOriginProtection": "disable"
}
روش دوم - کد #C در WebHostBuilder:
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseSetting("CrossOriginProtection", "disable");
روش سوم - حذف سرویس از DI (روش غیرمستقیم):
builder.Services.RemoveAll<ICsrfProtection>();

جدول مقایسه: سیستم جدید (Fetch Metadata CSRF) در برابر سیستم سنتی (Token Antiforgery)

معیار مقایسهسیستم جدید Cross-Origin Protection (.NET 11)سیستم سنتی Token-Based Antiforgery
مبنای امنیتهدرهای استاندارد مرورگر (Sec-Fetch-Site, Origin)توکن‌های رمزنگاری‌شده (Hidden Input / Cookie)
نیازمندی به پیکربندیصفر (Zero Config به‌صورت پیش‌فرض)نیازمند تزریق میدل‌ور و افزودن TagHelper در فرم‌ها
تأثیر بر کارایی (Performance)فوق‌العاده سبک، بدون حالت (Stateless)نیازمند پردازش رمزنگاری و تولید توکن
پشتیبانی از کلاینت‌های غیر مرورگریبه‌صورت خودکار مجاز دانسته می‌شوندنیازمند مدیریت هدرهای سفارشی یا استثنا کردن مسیر
مکانیسم رد درخواستارزیابی تنبل (Lazy Evaluation) هنگام خواندن فرمقطع زودهنگام در صورت عدم وجود توکن معتبر

نتیجه‌گیری
معرفی Automatic Cross-Origin CSRF Protection در NET 11 Preview 6. گامی بلند به سوی تحقق اصل «امنیت به‌صورت پیش‌فرض» (Secure by Default) در فریم‌ورک ASP.NET Core است. تیم مایکروسافت با هوشمندی و با تکیه بر استانداردهای مدرن مرورگرها، توانسته است بدون تحمیل بار پردازشی اضافی یا پیچیدگی‌های تنظیماتی، اکثریت برنامه‌های وب را در برابر حملات CSRF ایمن سازد. یکپارچگی این سیستم با الگوی Lazy Evaluation و امکان تعامل مسالمت‌آمیز آن با سیستم سنتی توکن‌ها، باعث می‌شود که برنامه‌های موجود بدون شکست در عملکرد (Breaking Changes غیرمنتظره) به این نسخه ارتقا یافته و توسعه‌دهندگان بتوانند تمرکز خود را بر روی منطق تجاری برنامه معطوف کنند.

نظرات

  • وحید نصیری در ۱۴۰۵/۰۴/۳۱ ۰۹:۵۵
    سناریوهای واقعی و مشکلاتی که یک توسعه‌دهنده هنگام ارتقای یک پروژه قدیمی با آن مواجه خواهد شد

    ارتقاء به .NET 11 برای برنامه‌هایی که از الگوهای استاندارد ASP.NET Core استفاده می‌کنند در اکثر مواقع بدون مشکل انجام می‌شود، اما به دلیل اضافه شدن این لایه محافظتی جدید و تغییر در رفتارهای زیرساختی (به‌ویژه الگوی Lazy Evaluation و اعتماد به میدل‌ورهای بالادستی)، برخی برنامه‌های قدیمی ممکن است دچار شکست در عملکرد (Breaking Changes) یا رفتارهای عجیب شوند. در ادامه، این موارد را بررسی می‌کنیم:

    ۱. از کار افتادن Webhookها و Callbacks از دامنه‌های خارجی (خطای HTTP 400)
    چه‌چیزی تغییر کرده؟
    برنامه‌های قدیمی ممکن است endpointهایی داشته باشند که اطلاعات را به صورت Form POST از سرویس‌های ثالث (مانند درگاه‌های پرداخت، سرویس‌های SMS، یا Webhook سامانه‌های دیگر) دریافت می‌کنند.
    مشکل احتمالی:
    چون سرویس ثالث درخواست را از یک دامنه دیگر (Cross-Origin) ارسال می‌کند و مرورگر هدر Origin یا Sec-Fetch-Site: cross-site را ست می‌کند، سیستم جدید CSRF این درخواست را غیرمجاز تشخیص می‌دهد. به محض اینکه برنامه سعی کند فرم را بخواند ([FromForm] یا Request.Form)، درخواست با خطای HTTP 400 Bad Request رد می‌شود.
    راهکار: باید روی این مسیرهای خاص، محافظت ضد جعل را غیرفعال کنید:
    app.MapPost("/api/payment-callback", ...).DisableAntiforgery();
    // یا در کنترلرها
    [IgnoreAntiforgeryToken]

    ۲. خطای Runtime در برنامه‌هایی کهapp.UseAntiforgery()را حذف کرده بودند
    چه‌چیزی تغییر کرده؟
    در نسخه‌های قبلی (مثلاً .NET 8/9)، برخی توسعه‌دهندگان به دلیل خطاهای مربوط به عدم وجود میدل‌ور antiforgery در مسیرهایی مانند MapRazorComponents()، طبق برخی ترفندها (Workarounds)، فراخوانی app.UseAntiforgery() را از فایل Program.cs حذف کرده بودند یا DisableCsrfProtection را تنظیم می‌کردند اما همچنان فرم‌های Blazor SSR داشتند.
    مشکل احتمالی:
    در .NET 11، کلاس RazorComponentEndpointInvokerدیگر خودش اعتبارسنجی توکن را انجام نمی‌دهد و کاملاً به حکم میان‌افزار بالادستی اعتماد می‌کند. اگر شخصی به صورت دستی این ویژگی جدید را با DisableCsrfProtection=true غیرفعال کرده باشد و فراخوانی app.UseAntiforgery() را هم در pipeline نداشته باشد، هنگام اجرای Blazor SSR با استثنا (Exception) مواجه می‌شود؛ زیرا سیستم متوجه می‌شود که هیچ میدل‌وری برای بررسی فرم‌ها اجرا نشده است!

    ۳. اختلال در معماری‌های Microfrontends یا برنامه‌هایی با چند دامنه (CORS / Multi-Domain)
    چه‌چیزی تغییر کرده؟
    سیستم جدید CSRF به صورت خودکار دامنه‌های مجاز را از سیاست پیش‌فرض CORS (Default CORS Policy) می‌خواند.
    مشکل احتمالی:
    اگر برنامه قدیمی شما:
    • از چند نام دامنه مختلف (مثلاً app.example.com و api.example.com) استفاده می‌کرده.
    • یا سیاست‌های CORS را به صورت نام‌گذاری‌شده (Named Policies) تعریف کرده بوده (builder.Services.AddCors(options => options.AddPolicy("MyPolicy", ...))).
    • یا در CORS از AllowAnyOrigin() استفاده می‌کرده.
    در این حالت، سیستم جدید CSRF سیاست‌های نام‌گذاری‌شده را نمی‌خواند (چون در CorsOptions قابلیت شمارش عمومی ندارند) و AllowAnyOrigin() را هم به دلایل امنیتی نادیده می‌گیرد. در نتیجه، درخواست‌های POST بین‌دامنه‌ای مشروع شما درون سازمان/شبکه خودتان، توسط سیستم جدید CSRF Denied تلقی شده و هنگام خواندن فرم با خطای 400 مواجه می‌شوند!
    راهکار: باید دامنه‌های موثق را حتماً در Default Policy مربوط به CORS ثبت کنید:
    builder.Services.AddCors(options => {
        options.AddDefaultPolicy(policy => {
            policy.WithOrigins("https://app.example.com");
        });
    });

    ۴. خطا در تست‌های یکپارچه‌سازی (Integration Tests) و ابزارهای تست API
    چه‌چیزی تغییر کرده؟
    ابزارهایی مانند HttpClient درون WebApplicationFactory در تست‌های #C، یا ابزارهایی مثل Postman و Insomnia معمولاً هدرهای مرورگر را ارسال نمی‌کنند.
    مشکل احتمالی:
    اگرچه کلاینت‌های بدون هدر به صورت پیش‌فرض مجاز (Allowed) هستند، اما اگر در تست‌های یکپارچه‌سازی خود به صورت دستی هدرهای Origin یا Referer غیرواقعی/ساختگی ست کرده باشید که با Host درخواست مطابقت نداشته باشد، تست‌های قدیمی شما که قبلاً پاس می‌شدند، اکنون با خطای 400 شکست خواهند خورد.

    ۵. رفتار نامتوازن در Endpointهایی که فرم را لود می‌کنند اما نمی‌خوانند (تغییر عمیق در اشکال‌زدایی)
    چه‌چیزی تغییر کرده؟
    الگوی Lazy Evaluation باعث می‌شود درخواست غیرمجاز تا زمانی که بدنه فرم پردازش نشود، رد نشود.
    مشکل احتمالی (سردرگمی توسعه‌دهنده هنگام دیباگ):
    توسعه‌دهنده ممکن است ببیند یک درخواست POST ناسالم به سرور می‌رسد، وارد میدل‌ورها می‌شود، حتی از لایه Authentication و Authorization عبور می‌کند و وارد کد کنترلر/اکشن می‌شود! توسعه‌دهنده فکر می‌کند همه چیز درست است، اما به محض اینکه در اولین سطر اکشن، کد await Request.ReadFormAsync() یا Model Binding فرم اجرا می‌شود، ناگهان درخواست قطعی شده و خطای ۴۰۰ برمی‌گرداند. این تغییر رفتار نسبت به سیستم‌های قدیمی که در همان ابتدای میدل‌ور درخواست را قطع (Short-circuit) می‌کردند، ممکن است باعث سردرگمی در دیباگ لاگ‌ها شود.

    جمع‌بندی: چک‌لیست ارتقا به .NET 11 برای توسعه‌دهندگان
    اگر برنامه‌ای را به .NET 11 ارتقا می‌دهید، این سه اقدام را حتماً انجام دهید:
    • بررسی Webhookها: روی تمام Endpointهایی که از خارج از برنامه داده‌های فرم دریافت می‌کنند، .DisableAntiforgery() بگذارید.
    • بررسی CORS: مطمئن شوید دامنه‌های مورد اعتماد شما در Default CORS Policy تعریف شده‌اند نه فقط در Named Policies.
    • عدم دستکاری میدل‌ورها: اگر از Blazor استفاده می‌کنید، نیازی به حذف یا اضافه‌کردن کدهای عجیب برای ضدجعل ندارید؛ بگذارید WebApplicationBuilder کار خود را به صورت پیش‌فرض انجام دهد.
  • وحید نصیری در ۱۴۰۵/۰۵/۰۸ ۰۸:۳۱
  • وحید نصیری در ۱۴۰۵/۰۵/۱۴ ۰۸:۳۳
    چالش‌های عملیاتی و جزییات فنی پیاده‌سازی مکانیزم دفاعی CSRF بر پایه Fetch Metadata در NET 11 (Preview 6).

    ۱. چرا سیستم توکن‌محور (Synchronizer Token) نیازمند جایگزینی بود؟
    اگرچه الگوی توکن‌های ضد جعل بیش از یک دهه استاندارد اصلی ASP.NET Core بوده است، اما ۳ چالش عملیاتی بزرگ در محیط‌های پیچیده ایجاد می‌کرد:
    وابستگی به Data Protection و چالش Stateful بودن:
    • سیستم توکن برای تولید و رمزنگاری کلیدها به زیرساخت Data Protection متکی است. در معماری‌های کلود (مثل Kubernetes) یا Web Farmها، همگام‌سازی و ذخیره‌سازی امن این کلیدها میان نسخه‌های متعدد (Replicas) نیاز به تنظیمات پیچیده (مانند Redis یا Azure Key Vault) دارد.
    مشکل تب‌های هم‌زمان (Multi-Tab Issue):
    • در الگوی توکن سنتی، معمولاً آخرین صفحه‌ای که بارگذاری می‌شود حاوی توکن معتبر است. اگر کاربر فرمی را در یک تب باز نگه دارد و در تب دیگر با برنامه کار کند، توکن تب اول باطل شده و با ارسال فرم خطای Invalid Token دریافت می‌کند.
    بار پردازشی و عدم امکان Caching مناسب:
    • عملیات رمزنگاری/داده‌گشایی توکن برای هر فرم و عدم امکان کش کردن صفحات استاتیک (به دلیل یکتا بودن توکن هر درخواست)، کارایی برنامه را کاهش می‌دهد.

    ۲. منطق دقیق الگوریتم جدید (DefaultCsrfProtection)
    در .NET 11، اینترفیست جدیدی به نام ICsrfProtection و پیاده‌سازی DefaultCsrfProtection معرفی شده که توسط CsrfProtectionMiddleware فراخوانی می‌شود. این الگوریتم که الگوی خود را از زبان Go 1.25 الهام گرفته است، درخواست را طی ۷ مرحله ارزیابی می‌کند:
    [ ورود درخواست به CsrfProtectionMiddleware ]
       │
       ├── ۱. آیا متد HTTP امن است؟ (GET, HEAD, OPTIONS, TRACE, QUERY) ──> [ مجاز ]
       ├── ۲. آیا Sec-Fetch-Site برابر same-origin یا none است؟ ───────> [ مجاز ]
       ├── ۳. آیا Origin در لیست مجاز CORS endpoint هست؟ ───────────────> [ مجاز ]
       ├── ۴. آیا Sec-Fetch-Site وجود دارد؟ (مقادیر cross-site / same-site) ──> [ مسدود ]
       ├── ۵. آیا Origin موجود است؟
       │       ├── تطابق Origin با هدر Host ─────────────────────────────> [ مجاز ]
       │       └── عدم تطابق Origin با Host ─────────────────────────────> [ مسدود ]
       └── ۶. هدرهای Sec-Fetch-Site و Origin وجود ندارند؟
               └── درخواست غیرمرورگری است (مثل cURL / Postman) ───────────> [ مجاز ]

    ۳. نحوه جلوگیری از خطاهای کاذب (False Positives)
    یکی از محاسن این میدل‌ور، نحوه مواجهه آن با درخواست‌های غیرمرورگری یا درخواست‌های غیرحساس است.
    حمله CSRF تنها زمانی رخ می‌دهد که سه شرط هم‌زمان برقرار باشند:
    • درخواست از طریق مرورگر ارسال شود.
    • احراز هویت مبتنی بر Cookie باشد.
    • داده‌ها به صورت فرم ارسال شوند.
    بنابراین، اگر میدل‌ور تشخیص دهد که درخواستی Cross-Site است، آن را بلافاصله قطع نمی‌کند؛ بلکه نتیچه‌ منفی را در HTTP Context ثبت کرده و تصمیم‌گیری نهایی را به زمانی می‌سپارد که برنامه قصد دارد بدنه درخواست را به عنوان فرم بخواند ([FromForm] یا Request.Form). اگر درخواست شامل بدنه JSON باشد یا توسط یک ابزار CLI ارسال شده باشد، خطای CSRF صادر نمی‌شود.

    ۴. چالش بزرگ: وضعیت MVC و Razor Pages در .NET 11
    بر خلاف Blazor SSR و Minimal APIها که در .NET 11 به صورت کامل و بومی به سیستم جدید سوئیچ کرده‌اند، برنامه‌های MVC و Razor Pages به صورت پیش‌فرض همچنان سیستم توکن سنتی را نیز کنار سیستم جدید اجرا می‌کنند.
    مایکروسافت این رویکرد را «دفاع عمقی (Defense in Depth)» می‌نامد، اما در عمل باعث می‌شود کدهای MVC بدون وجود توکن در فرم همچنان خطای 400 Bad Request بدهند.

    راهکار آزمایشگاهی برای حذف کامل توکن از MVC / Razor Pages
    اگر بخواهید توکن‌ها را به طور کامل از پروژه MVC یا Razor Pages خود حذف کرده و صرفاً به Fetch Metadata متکی شوید، باید ۳ تغییر زیر را اعمال کنید:

    ۱: غیرفعال کردن تولید خودکار توکن در تگ‌های HTML
    با پیاده‌سازی یک ITagHelperInitializer سفارشی، تولید توکن برای تمام تگ‌های غیرفعال می‌شود:
    public class DisableAntiForgeryTagHelperInitializer : ITagHelperInitializer<FormTagHelper>
    {
        public void Initialize(FormTagHelper helper, ViewContext context)
        {
            // غیرفعال کردن ساخت input مخفی توکن
            helper.Antiforgery = false;
        }
    }
    
    // ثبت در DI Container:
    builder.Services.AddSingleton<ITagHelperInitializer<FormTagHelper>>(
        new DisableAntiForgeryTagHelperInitializer());

    ۲: حذف فیلتر اعتبارسنجی خودکار توکن
    حذف فیلتر پیش‌فرض AutoValidateAntiforgeryTokenAttribute از الگوی MVC و Razor Pages از طریق یک Convention:
    public class RemoveAntiforgeryConvention : IPageApplicationModelConvention, IControllerModelConvention
    {
        public void Apply(PageApplicationModel model) => RemoveFilter(model.Filters);
        public void Apply(ControllerModel controller) => RemoveFilter(controller.Filters);
    
        private static void RemoveFilter(IList<IFilterMetadata> filters)
        {
            var antiforgeryFilter = filters.FirstOrDefault(f => f is AutoValidateAntiforgeryTokenAttribute);
            if (antiforgeryFilter != null)
            {
                filters.Remove(antiforgeryFilter);
            }
        }
    }
    
    // اضافه کردن Convention در تنظیمات:
    builder.Services.AddRazorPages(options => options.Conventions.Add(new RemoveAntiforgeryConvention()));
    builder.Services.AddControllersWithViews(options => options.Conventions.Add(new RemoveAntiforgeryConvention()));

    ۳: جایگزینی فیلتر اعتبارسنجی مبتنی بر Fetch Metadata
    ایجاد یک فیلتر جدید که به جای بررسی توکن، وضعیت ثبت‌شده توسط CsrfProtectionMiddleware را ارزیابی کند:
    public class CsrfProtectionAuthorizationFilter : IAuthorizationFilter, IAntiforgeryPolicy
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            // بررسی وضعیت اعتبارسنجی که قبلاً توسط CsrfProtectionMiddleware ثبت شده است
            var validationFeature = context.HttpContext.Features.Get<IAntiforgeryValidationFeature>();
            
            if (validationFeature is { IsValid: false })
            {
                // رد کردن درخواست در صورت غیرمجاز بودن Cross-Site
                context.Result = new AntiforgeryValidationFailedResult();
            }
        }
    }
    هشدار امنیتی: دستکاری فوق یک راهکار آزمایشگاهی برای یکپارچه‌سازی کامل MVC است. در محیط‌های عملیاتی (Production)، توصیه می‌شود تا انتشار نسخه‌های نهایی .NET 11 و پشتیبانی رسمی مایکروسافت، در MVC از رفتار پیش‌فرض (دفاع دو لایه‌ای) استفاده کنید.
  • وحید نصیری در ۱۴۰۵/۰۶/۱۱ ۰۸:۱۷
    تحلیل تکمیلی اثرات سیستم CSRF خودکار در .NET 11 بر معماری‌های SPA، احراز هویت و پروتکل‌های مدرن

    لایه محافظتی پیش‌فرض .NET 11 فراتر از فرم‌های وب سنتی اثرگذار است. در معماری‌های مدرن مبتنی بر پروتکل‌های هویتی (مانند OAuth 2.0 و OpenID Connect) یا برنامه‌های تک‌صفحه‌ای (SPA)، تبادل داده‌ها ذاتاً ماهیت بین‌دامنه‌ای (Cross-Origin) دارد. در ادامه، ابعاد معمارانه، سناریوهای شکست در توسعه محلی، کلاینت‌های هیبریدی و نقاط تقاطع با پروتکل‌های هویتی که پیش‌تر بررسی نشدند، تشریح شده‌اند.

    ۱. توسعه محلی SPA: اجباری شدن Reverse Proxy در محیط Development
    در چرخه‌های توسعه متداول فرانت‌اند (React، Angular، Vue)، سرورهای توسعه (مانند Vite یا Webpack DevServer) روی درگاه متفاوتی نسبت به وب‌سرویس بک‌اند اجرا می‌شوند (برای نمونه http://localhost:5173 در برابر https://localhost:5001).
    • رفتار قبلی: توسعه‌دهندگان صرفاً با تنظیم خط‌مشی‌های CORS، درخواست‌های بین درگاهی را در مرورگر مجاز می‌کردند.
    • تغییر در .NET 11: چون درگاه‌های متفاوت از دیدگاه مرورگر Cross-Origin محسوب می‌شوند، درخواست‌های POST، PUT، PATCH یا DELETE هدر Sec-Fetch-Site: cross-site دریافت کرده و توسط لایه جدید CSRF رد می‌شوند.

    راهکار توسعه: اتکا به CORS در محیط محلی دیگر کافی نیست. سرورهای توسعه فرانت‌اند باید به گونه‌ای پیکربندی شوند که درخواست‌های API را پروکسی کنند تا ارتباط از دید مرورگر کاملاً Same-Origin به نظر برسد:
    // نمونه پیکربندی در vite.config.js
    export default {
      server: {
        proxy: {
          '/api': {
            target: 'https://localhost:5001',
            changeOrigin: true,
            secure: false
          }
        }
      }
    }

    ۲. سناریوهای تلاقی با جریان‌های OAuth 2.0 و OpenID Connect
    زیرساخت‌های مدیریت هویت و ارائه‌دهندگان توکن (خواه سفارشی و خواه فریم‌ورک‌های اختصاصی Identity Server) متکی بر استانداردهایی هستند که تبادل داده بین دامنه‌ها را الزامی می‌کنند. لایه امنیتی جدید می‌تواند چندین بخش کلیدی از این چرخه‌ها را مسدود کند:
    الف) جریان کد مجوز همراه با PKCE در SPAها
    الگوی استاندارد Authorization Code + PKCE ایجاب می‌کند که پس از بازگشت مرورگر از سرور هویت، اسکریپت‌های کلاینت (جاوااسکریپت) مستقیماً با فراخوانی متد fetch()، یک درخواست POST حاوی کد مجوز را به مسیر /connect/token ارسال کنند.
    چون این درخواست از دامنه SPA به دامنه Identity Server ارسال می‌شود، مرورگر هدر Sec-Fetch-Site: cross-site را به آن الحاق می‌کند و سیستم جدید .NET 11 تبادل توکن را متوقف خواهد کرد.

    ب) بازگشت از ارائه‌دهندگان خارجی (External IdP Callbacks)
    هنگام استفاده از ورود یکپارچه با سیستم‌هایی مانند Microsoft Entra ID یا Google با تنظیم response_mode=form_post، ارائه‌دهنده خارجی توکن یا کد را به صورت یک فرم POST متقاطع به مسیر /signin-oidc برنامه شما ارسال می‌کند. نحوه برخورد میدل‌ور با فیلد Sec-Fetch-Mode و ساختار ناوبری در این مرحله حیاتی است و نیازمند استثنا شدن این مسیرها در پایپ‌لاین احراز هویت است.

    ج) خروج از سیستم از طریق کانال مستقیم (Front-Channel Logout)
    در سناریوهای Single Sign-Out، سرور هویت معمولاً جهت اطلاع‌رسانی خروج به سایر کلاینت‌ها، داخل صفحه‌ای پنهان چندین iframe به دامنه‌های مختلف کلاینت‌ها باز می‌کند. با ترکیب Sec-Fetch-Dest: iframe و Sec-Fetch-Site: cross-site، امکان دارد اعلان‌های خروج به صورت خاموش توسط سرورهای دریافت‌کننده رد شوند و وضعیت خروج کاربر با شکست مواجه گردد.

    د) پایانه‌های دریافت توکن و اعتبارسنجی بین‌سروری
    مسیرهایی مانند:
    • پایانه‌های ابطال توکن (Revocation Endpoint)
    • پایانه‌های بررسی صحت توکن (Introspection Endpoint)
    • پایانه‌های تفویض اختیار یا تبادل توکن سفارشی (RFC 8693 Token Exchange)

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

    ۳. بازنگری معماری با الگوی Backend-for-Frontend (BFF)
    تنش‌های ایجادشده میان الزامات امنیتی جدید و ماهیت تبادلات SPA، حرکت به سمت الگوی معمارانه BFF (Backend-for-Frontend) را سرعت می‌بخشد.
    ┌─────────────────────────────────────────────────────────┐
    │                      مرورگر کاربر                          
    │  ┌───────────────────────┐   Same-Origin POST / PUT     
    │  │   برنامه تک‌صفحه‌ای   │─────────────────────────┐         
    │  │         (SPA)         │                    │    
    │  └───────────────────────┘                    │    
    └───────────────────────────────────────────────┼─────────┘
                                                    ▼
    ┌─────────────────────────────────────────────────────────┐
    │              لایه میزبان BFF (Same-Origin)                  
    │                                                         
    │  - دریافت درخواست‌ها در دامنه اصلی                              
    │  - مدیریت کوکی‌های فقط-سرور (HttpOnly Cookies)               
    │  - حذف اصطکاک هدرهای Sec-Fetch-Site                      
    └────────────────────────────┬────────────────────────────┘
                                 │
                      Server-to-Server (بدون هدر مرورگر)
                                 │
                                 ▼
    ┌─────────────────────────────────────────────────────────┐
    │       وب‌سرویس‌های تجاری / سرویس احراز هویت (IdP)              
    └─────────────────────────────────────────────────────────┘
    در این الگو، برنامه فرانت‌اند و لایه سروری BFF روی یک منبع مشترک (دامنه و پورت یکسان، توسط یک Reverse Proxy نظیر YARP یا Nginx) مستقر می‌شوند. مزیت بنیادین این ساختار در .NET 11 این است که:
    • ارتباط مرورگر با BFF همیشه Same-Origin بوده و دچار تداخل با قوانین CSRF نمی‌شود.
    • ارتباط BFF با سرور هویت یا سایر میکروسرویس‌ها به‌صورت Server-to-Server انجام می‌گیرد؛ بنابراین مرورگر در آن نقشی ندارد و هدرهای مرورگری مسبب مسدودسازی ارسال نخواهند شد.

    ۴. کلاینت‌های هیبریدی و WebView (مانند MAUI و Capacitor)
    چارچوب‌های تولید برنامه‌های چندسکویی موبایل یا دسکتاپ که از وب‌ویو برای رابط کاربری استفاده می‌کنند (مانند .NET MAUI Blazor Hybrid، Capacitor یا Electron)، رفتارهای غیریکدستی در ارسال هدرهای W3C Fetch Metadata دارند:
    • برخی بسترهای WebView محلی، فاقد ارسال هدر Sec-Fetch-Site هستند (که در این حالت، فریم‌ورک آن‌ها را کلاینت غیر مرورگری فرض کرده و مجاز می‌داند).
    • برخی دیگر دامنه محلی را با پیشوندهایی مثل capacitor://localhost یا app:// ارسال می‌کنند که سبب ایجاد یک مقدار نامتعارف در هدر Origin و مغایرت با Host سرور می‌شود.

    نکته کلیدی: برنامه‌هایی که بک‌اند .NET 11 دارند اما سرویس‌گیرنده آن‌ها اپلیکیشن‌های هیبریدی بر پایه WebView هستند، باید حتماً رفتار این کلاینت‌ها را در ارسال هدرها بررسی کرده و در صورت رد درخواست، مبدأهای داخلی وب‌ویو را در لیست دامنه‌های مجاز قرار دهند یا از پیاده‌سازی سفارشی ICsrfProtection استفاده کنند.

    ۵. راهبرد اعتبارسنجی در فاز Migration: روش تشخیص و ایزولاسیون خطا
    یکی از چالش‌های عیب‌یابی در .NET 11 این است که ابزارهای تست خودکار سنتی (مانند HttpClient بدون سرصفحه یا تست‌های مبتنی بر خط فرمان) هدر Sec-Fetch-Site را تولید نمی‌کنند و تمامی تست‌ها در پایپ‌لاین CI/CD موفقیت‌آمیز خواهند بود، اما کاربر نهایی در مرورگر با خطای ۴۰۰ مواجه می‌شود.

    برای جداسازی مشکل هنگام مهاجرت:

    گام اول: استفاده از فلگ تشخیصی
    اگر پس از ارتقا، رفتارهای پیش‌بینی‌نشده‌ای در ارتباطات فرانت‌اند یا لاگین رخ داد، ابتدا این ویژگی را موقتاً در تنظیمات غیرفعال کنید تا منشأ مشکل تأیید شود:
    {
      "CrossOriginProtection": "disable"
    }

    گام دوم: استثنا کردن هدفمند، نه غیرفعال‌سازی سراسری
    پس از اطمینان از اینکه علت شکست، میدل‌ور CSRF است، فلگ فوق را به وضعیت فعال بازگردانده و صرفاً پایانه‌های خاص را مستثنی کنید:
    // مستثنی‌سازی مسیر تبادل توکن یا فرآیندهای بازگشتی در Minimal APIs
    app.MapPost("/connect/token", HandleTokenExchange)
       .DisableAntiforgery();
    این رویکرد تضمین می‌کند که سایر بخش‌های سامانه از مزایای محافظت خودکار و پیش‌فرض .NET 11 بهره‌مند بمانند، بدون اینکه جریان‌های ارتباطی معتبر آسیب ببینند.