درک هدرهای HTTP متادیتای فچ (Fetch Metadata)
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۵/۰۸ ۰۸:۲۵
آدرس: www.dntips.ir
چکیده: با پیچیدهتر شدن تهدیدات امنیتی در بستر وب، مکانیزمهای سنتی مانند توکنهای ضد جعل درخواست سمت سرور (Anti-CSRF Tokens) یا کوکیهایSameSiteبه تنهایی برای مقابله با حملات پیشرفته کافی نیستند. مشخصه Fetch Metadata HTTP Request Headers که از سال ۲۰۱۹ توسط W3C معرفی شده و در سالهای اخیر به پختگی کامل رسیده است، چارچوبی نوین ارائه میدهد. در این چارچوب، مرورگر همراه با هر درخواست HTTP، متادادههایی شامل مبدأ، هدف و نحوه شکلگیری درخواست ارسال میکند. این مقاله به بررسی ساختار و کاربرد ۴ هدر اصلی یعنیSec-Fetch-Site،Sec-Fetch-Dest،Sec-Fetch-ModeوSec-Fetch-Userمیپردازد. همچنین، نحوه پیادهسازی سیاست جداسازی منابع (Resource Isolation Policy) و چرایی اهمیت این مشخصه برای توسعهدهندگان مایکروسافت NET 11. و ASP.NET Core تحلیل شده است.
ویژگی حیاتی: هدرهای Fetch Metadata توسط مرورگر به صورت خودکار و سطح پایین (Low-level) تنظیم میشوند و بههیچعنوان از طریق APIهای جاوااسکریپت (مانندfetchیاAxios) قابل تغییر یا دستکاری نیستند. بنابراین، اطلاعات دریافت شده در سمت سرور صددرصد قابل اعتماد (Trusted) هستند.
[ HTTP Request Headers ] ├── Sec-Fetch-Dest : مقصد پاسخ (image, script, document, ...) ├── Sec-Fetch-Site : رابطه مبدأ و مقصد (same-origin, cross-site, ...) ├── Sec-Fetch-Mode : حالت درخواست (navigate, cors, no-cors, ...) └── Sec-Fetch-User : نقش مستقیم کاربر در شروع درخواست (?1)
Sec-Fetch-Dest(مقصد درخواست)document: درخواست ناشی از ناوبری سطح بالا (Top-level Navigation) است (مثل کلیک روی لینک یا وارد کردن URL در آدرسبار) و صفحه فعلی را جایگزین میکند.empty: درخواست مقصد تجسمی در HTML ندارد. معمولاً توسط فراخوانیهای fetch()، XMLHttpRequest یا WebSocket ایجاد میشود.image: پاسخ به عنوان تصویر استفاده خواهد شد (تگ <img>، تصویر پسزمینه در CSS و...).script ،style ،font ،iframe ،audio ،video ،worker.کاربرد امنیتی: اگر سرور درخواستی برای یک فایل حساسی مانندjson.یاjs.دریافت کند اما هدرSec-Fetch-Destمقدارimageداشته باشد، مشخص است که درخواستی غیرعادی (احتمالاً یک حمله) در جریان است و سرور میتواند بلافاصله آن را مسدود کند.
Sec-Fetch-Site(رابطه مبدأ و مقصد)same-origin: مبدأ و مقصد کاملاً یکسان هستند (پروتکل، دامنه، زیردامنه و پورت یکسان).same-site: پروتکل و دامنه یکسان هستند، اما ممکن است زیردامنه یا پورت متفاوت باشد.cross-site: مبدأ و مقصد از دو سایت کاملاً متفاوت هستند.none: درخواست تعاملی نبوده و از صفحهای دیگر شروع نشده است (مانند تایپ مستقیم URL یا باز کردن از Bookmark).same-origin و same-site مقایسه زیر را نسبت به مقصد [http://www.example.org](http://www.example.org) ببینید:| URL مبدأ | توضیحات | Same-Site | Same-Origin |
[http://www.example.org](http://www.example.org) | آدرس کاملاً یکسان | ✅ | ✅ |
[http://www.example.org:8080](http://www.example.org:8080) | پورت متفاوت | ✅ | ❌ |
[http://sub.example.org](http://sub.example.org) | زیردامنه متفاوت | ✅ | ❌ |
[https://www.example.org](https://www.example.org) | پروتکل متفاوت (HTTPS) | ❌ | ❌ |
[http://www.evil.org](http://www.evil.org) | دامنه متفاوت | ❌ | ❌ |
Sec-Fetch-Mode(حالت اجرای درخواست)navigate: برای درخواستهای ناوبری بین صفحات HTML استفاده میشود.same-origin: درخواستهای درونبرنامهای که نیاز به CORS ندارند.cors: درخواستهای Cross-Origin که با استاندارد CORS ارسال میشوند. پاسخ تنها در صورت وجود هدرهای Access-Control-Allow-Origin توسط جاوااسکریپت قابل خواندن است.no-cors: درخواستهای Cross-Origin ساده (مانند بارگذاری تصویر یا استایل). مرورگر اجازه خواندن پاسخ را به جاوااسکریپت نمیدهد (پاسخ Opaque یا کدر است).websocket: درخواست برای برقراری ارتباط WebSocket.نکته تکمیلی: هنگامی که مرورگر یک تگ img را بارگذاری میکند، درخواست در حالتno-corsارسال میشود. سرور اگر بداند یک API حساس (مثلاً/api/delete-user) فقط باید از طریق JS و حالتcorsفراخوانی شود، اما درخواستی با حالتno-corsبرای آن بیاید، میتواند آن را فوراً رد کند.
Sec-Fetch-User(تعامل مستقیم کاربر)1? خواهد بود. در غیر این صورت، این هدر کلاً ارسال نمیشود.Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: none Sec-Fetch-User: ?1
Sec-Fetch-Dest: style Sec-Fetch-Mode: same-origin Sec-Fetch-Site: same-origin
Sec-Fetch-Dest: image Sec-Fetch-Mode: no-cors Sec-Fetch-Site: cross-site
Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: cross-site
[ ورود درخواست به سرور ]
│
├── آیا Sec-Fetch-Site برابر same-origin یا same-site یا none است؟
│ ├── بله ──> [ مجاز ]
│ └── خیر ──> آیا یک ناوبری سطح بالا است؟ (Dest=document AND Mode=navigate AND Method=GET)
│ ├── بله ──> [ مجاز (کاربر روی لینک کلیک کرده) ]
│ └── خیر ──> [ مسدودسازی (درخواست Cross-Site غیرمجاز) ]public class FetchMetadataValidationMiddleware
{
private readonly RequestDelegate _next;
public FetchMetadataValidationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var request = context.Request;
// اگر هدر وجود نداشته باشد (مرورگرهای بسیار قدیمی)، اجازه عبور میدهیم یا طبق سیاست برخورد میکنیم
if (request.Headers.TryGetValue("Sec-Fetch-Site", out var siteValues))
{
var site = siteValues.ToString();
// ۱. درخواستهای درونبرنامهای یا آدرسبار همیشه مجاز هستند
if (site is "same-origin" or "same-site" or "none")
{
await _next(context);
return;
}
// ۲. درخواستهای Cross-Site فقط در صورتی مجاز هستند که ناوبری ساده GET باشند
var dest = request.Headers["Sec-Fetch-Dest"].ToString();
var mode = request.Headers["Sec-Fetch-Mode"].ToString();
bool isTopLevelNavigation = HttpMethods.IsGet(request.Method)
&& dest == "document"
&& mode == "navigate";
if (!isTopLevelNavigation)
{
// مسدودسازی درخواستهای مشکوک Cross-Site (مثل POST از سایت دیگر)
context.Response.StatusCode = StatusCodes.Status403Forbidden;
await context.Response.WriteAsync("Cross-Site Request Blocked by Fetch Metadata Policy.");
return;
}
}
await _next(context);
}
}Sec-Fetch-* به توسعهدهندگان ASP.NET Core کمک میکند تا علاوه بر درک بهتر مکانیزمهای داخلی فریمورک، برنامههایی امنتر و با معماری مدرنتر طراحی کنند.Sec-Fetch-Site و Origin) از برنامه محافظت میکند. در ادامه، نحوه پیادهسازی این سیستم در .NET 11 و مقایسه همهجانبه آن با روش سنتی توکنمحور بررسی شده است.WebApplication.CreateBuilder ساخته میشوند، میدلور محافظت خودکار CSRF به طور خودکار در خط لوله (Pipeline) قرار میگیرد. این سیستم نیازی به تولید، نگهداری یا ارسال توکنهای مخفی در فرمها ندارد.GET ،HEAD ،OPTIONS و TRACE همیشه مجاز هستند (طبق استاندارد RFC 9110). Sec-Fetch-Site مقدار same-origin (ناوبری داخلی) یا none (ورود مستقیم از آدرسبار) داشته باشد، درخواست تایید میشود. Origin باشد و آن Origin در سیاستهای CORS برنامه ثبت شده باشد، مجاز تلقی میشود. Sec-Fetch-Site مقادیر cross-site یا same-site داشته باشد و مبدأ آن در CORS تایید نشده باشد، درخواست نامعتبر (Invalid) علامتگذاری میشود. Sec-Fetch-Site و Origin وجود نداشته باشند (مثل درخواستهای Postman ،cURL یا اپلیکیشنهای موبایل)، درخواست مجاز شناخته میشود (زیرا CSRF یک آسیبپذیری وابسته به مرورگر است). مفهوم Deferred Validation (اعتبارسنجی بهتعویقافتاده):
این میدلور درخواست غیرمجاز را بلافاصله قطع نمیکند؛ بلکه یک نشانگر (Verdict) نامعتبر رویIAntiforgeryValidationFeatureدرخواست ثبت میکند. سپس هنگامی که اجزای پردازش فرم (مانند Razor Pages، کنترلرهای MVC، Blazor SSR یا Form Binding در Minimal API) قصد خواندن بدنه فرم را دارند، این نشانگر را چک کرده و پاسخ400 Bad Requestبرمیگردانند.
| ویژگی / محور مقایسه | سیستم جدید (Fetch Metadata) | سیستم سنتی (Anti-Forgery Token) |
| مبنای امنیت | هدرهای غیرقابل تغییر مرورگر (Sec-Fetch-Site, Origin) | توکنهای رمزنگاریشده (Synchronizer Token Pattern) |
| نیاز به تغییر کد HTML | ❌ خیر (به هیچ Input مخفی یا هدری نیاز نیست) | ✅ بله (نیاز به یا هدر سفارشی) |
| بار پردازشی سرور | بسیار سبک (صرفاً بررسی هدرهای متنی) | سنگینتر (تولید، رمزنگاری و اعتبارسنجی توکنها) |
| وابستگی به Data Protection | ❌ ندارد | ✅ دارد (در Server Farm نیاز به کلیدهای مشترک دارد) |
| مشکلات Caching / CDN | ❌ ندارد (پاسخهای HTML کاملاً استاتیک باقی میمانند) | ⚠️ دارد (تولید توکن یکتا مانع Caching صفحات میشود) |
| پشتیبانی از مرورگرها | تمام مرورگرهای مدرن (Chrome, Firefox, Safari, Edge) | تمام مرورگرها (حتی مرورگرهای قدیمی بدون پشتیبانی از Fetch Metadata) |
| کلاینتهای غیر مرورگر | به صورت خودکار bypass میشوند | نیاز به استثنا کردن یا مدیریت توکن دارند |
var builder = WebApplication.CreateBuilder(args);
// میدلورهای MVC یا Razor Pages یا Minimal API
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.UseRouting();
app.UseAuthorization();
// محافظت CSRF مبتنی بر Fetch Metadata به صورت خودکار فعال است
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();[https://trusted-partner.com](https://trusted-partner.com)) قرار است فرمهایی را به API شما ارسال کند، میتوانید آن را از طریق CORS مجاز کنید:builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin", policy =>
{
policy.WithOrigins("https://trusted-partner.com")
.AllowAnyMethod()
.AllowAnyHeader();
});
});
// اعمال سیاست روی انتهای مورد نظر
app.MapPost("/submit-partner-form", (MyFormModel data) => Results.Ok())
.RequireCors("AllowSpecificOrigin");// در Minimal API
app.MapPost("/api/webhook", (WebhookData data) => Results.Ok())
.DisableAntiforgery();
// در کنترلرهای MVC / Razor Pages
[HttpPost]
[IgnoreAntiforgeryToken]
public IActionResult WebhookReceiver([FromBody] WebhookData data)
{
return Ok();
}// فعالسازی خدمات توکن سنتی در صورت نیاز
builder.Services.AddAntiforgery(options =>
{
options.HeaderName = "X-XSRF-TOKEN";
});
// استفاده از ویژگی سنتی روی اکشنهای خاص
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult LegacyProtectedAction()
{
return Ok();
}