مقالات › امنیت

اصول امنیت وب‌اپلیکیشن؛ OWASP Top 10 برای توسعه‌دهندگان ‎.NET و React

برنامه‌تان را در برابر رایج‌ترین حملات محافظت کنید: SQL Injection، XSS، CSRF، نقص کنترل دسترسی، IDOR، افشای کلیدها و پیکربندی نادرست؛ با راهکارهای عملی در ‎.NET و React.

امنیت ویژگی‌ای نیست که آخر کار اضافه شود؛ مجموعه‌ای از عادت‌هاست که در هر خط کد رعایت می‌کنید. بیشتر نفوذهای واقعی از اکسپلویت‌های عجیب استفاده نمی‌کنند؛ از اشتباهات معمولی سوءاستفاده می‌کنند: کوئری‌ای که با چسباندن رشته ساخته شده، Endpointی که مالکیت را بررسی نمی‌کند یا کلیدی که در Git ثبت شده است. این راهنما رایج‌ترین ریسک‌های OWASP Top 10 را مرور می‌کند و دفاع‌های مشخص در ASP.NET Core و React را نشان می‌دهد.

۱. نقص کنترل دسترسی (و IDOR)

ریسک شماره‌ی یک در برنامه‌های امروزی: کاربر به داده یا عملیاتی دسترسی پیدا می‌کند که نباید.

// ❌ هر کاربر واردشده با تغییر id می‌تواند هر سفارشی را بخواند
[Authorize]
[HttpGet("orders/{id:int}")]
public async Task<IActionResult> Get(int id) =>
    Ok(await db.Orders.FindAsync(id));

این یک IDOR (ارجاع مستقیم ناامن به شیء) است. احراز هویت با مجوزدهی یکی نیست؛ مالکیت را بررسی کنید:

// ✅ هر کوئری را به کاربر فعلی محدود کنید
[Authorize]
[HttpGet("orders/{id:int}")]
public async Task<IActionResult> Get(int id)
{
    var userId = User.GetUserId();
    var order = await db.Orders
        .Where(o => o.Id == id && o.CustomerId == userId)
        .Select(o => new OrderDto(o.Id, o.Total, o.Status))
        .FirstOrDefaultAsync();

    return order is null ? NotFound() : Ok(order);
}

قوانین:

  • پیش‌فرض را «عدم دسترسی» بگذارید؛ کنترلرها را با [Authorize] محافظت کنید و فقط چیزهایی را باز کنید که باید عمومی باشند.
  • مجوزدهی را در سرور اعمال کنید؛ مخفی کردن یک دکمه در React از هیچ چیز محافظت نمی‌کند.
  • دسترسی را برای هر عملیات، از جمله ویرایش و حذف، بررسی کنید.

۲. تزریق (SQL Injection)

// ❌ هرگز SQL را با ورودی کاربر نسازید
var sql = $"SELECT * FROM Users WHERE Email = '{email}'";
// email = "' OR 1=1 --"  ←  همه‌ی کاربران برگردانده می‌شوند

همیشه از پارامتر استفاده کنید:

// ✅ کوئری‌های LINQ در EF Core پارامتری هستند
var user = await db.Users.FirstOrDefaultAsync(u => u.Email == email);

// ✅ SQL خام با Interpolation در FromSql پارامتری می‌شود
var users = await db.Users.FromSql($"SELECT * FROM Users WHERE Email = {email}").ToListAsync();

// ✅ Dapper
var u = await conn.QuerySingleOrDefaultAsync<User>(
    "SELECT * FROM Users WHERE Email = @Email", new { Email = email });

هشدار: FromSqlRaw همراه با چسباندن رشته امن نیست. فقط FromSql/ExecuteSql با Interpolation (یا SqlParameter صریح) پارامتری هستند.

همین اصل برای دستورهای سیستم‌عامل، کوئری‌های LDAP و ستون‌های پویای ORDER BY صادق است؛ به‌جای قرار دادن ورودی، مقادیر مجاز را لیست سفید کنید.

۳. اسکریپت‌نویسی بین‌سایتی (XSS)

XSS وقتی رخ می‌دهد که متن تحت کنترل مهاجم در مرورگر کاربر دیگری به‌عنوان HTML/جاوااسکریپت اجرا شود؛ برای دزدیدن توکن یا انجام کار به‌جای قربانی.

React به‌طور پیش‌فرض مقادیر داخل JSX را Escape می‌کند:

<p>{comment.text}</p>   // ✅ به‌صورت متن نمایش داده می‌شود، حتی اگر <script> داشته باشد

مناطق خطر:

// ❌ HTML خام از کاربر
<div dangerouslySetInnerHTML={{ __html: comment.html }} />

// ✅ اول پاک‌سازی کنید
import DOMPurify from "dompurify";
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(comment.html) }} />

// ❌ آدرس تحت کنترل کاربر می‌تواند "javascript:..." باشد
<a href={profile.website}>Website</a>

آدرس‌ها را اعتبارسنجی کنید (فقط http: و https:) و متن غنی را در سرور هم پاک‌سازی کنید (مثلاً با پکیج HtmlSanitizer). یک هدر Content Security Policy اضافه کنید تا محل بارگذاری اسکریپت‌ها محدود شود:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'

۴. جعل درخواست بین‌سایتی (CSRF)

اگر احراز هویت با کوکی انجام شود، یک سایت دیگر می‌تواند درخواست‌هایی ایجاد کند که مرورگر همراه با کوکی‌های کاربر می‌فرستد. دفاع‌ها:

  • کوکی‌های SameSite=Lax یا Strict (پیش‌فرض امروزی مرورگرها).
  • توکن Anti-forgery برای فرم‌های مبتنی بر کوکی ([ValidateAntiForgeryToken] در MVC و داخلی در Razor Pages).
  • هرگز با درخواست GET وضعیت را تغییر ندهید.

APIهایی که از هدر Authorization: Bearer استفاده می‌کنند در برابر CSRF کلاسیک آسیب‌پذیر نیستند، چون مرورگر این هدر را خودکار ضمیمه نمی‌کند.

۵. ضعف‌های رمزنگاری

  • HTTPS همه‌جا همراه با HSTS (app.UseHsts()).
  • رمز عبور را هش کنید با الگوریتم‌های کند و نمک‌دار: PasswordHasher در ASP.NET Core Identity، PBKDF2، BCrypt یا Argon2. هرگز MD5/SHA1 و هرگز رمزنگاری برگشت‌پذیر.
  • داده‌های حساس را در صورت نیاز در حالت ذخیره رمز کنید (مثل کد ملی) و کلیدها را با سرویس مدیریت کلید یا Data Protection محافظت کنید.
  • توکن‌ها را با RandomNumberGenerator بسازید، نه Random.

۶. پیکربندی نادرست امنیتی

  • صفحه‌ی خطای توسعه‌دهنده را در Production نشان ندهید؛ خطای عمومی همراه با traceId برگردانید.
  • Swagger UI را در Production غیرفعال یا محافظت کنید.
  • حساب‌های پیش‌فرض و Endpointهای نمونه را حذف کنید.
  • CORS را به دامنه‌های شناخته‌شده محدود کنید:
builder.Services.AddCors(o => o.AddPolicy("web", p => p
    .WithOrigins("https://alimahmoodi.ir")
    .AllowAnyHeader()
    .AllowAnyMethod()));
  • هدرهای امنیتی اضافه کنید: X-Content-Type-Options: nosniff، Referrer-Policy و frame-ancestors (محافظت در برابر Clickjacking).

۷. مدیریت کلیدها و رمزها

❌ ثبت appsettings.json همراه با Connection String و کلید API واقعی در Git
❌ کلید API داخل کد React (هر چیزی در Bundle عمومی است!)
  • محیط توسعه: dotnet user-secrets.
  • Production: متغیرهای محیطی یا یک Vault (مثل Azure Key Vault یا HashiCorp Vault).
  • فرانت‌اند: هرگز رمز نفرستید؛ بک‌اند خودتان را صدا بزنید که کلید را نگه می‌دارد.
  • اگر رمزی لو رفت، آن را عوض کنید؛ حذف از تاریخچه‌ی Git کافی نیست.

۸. وابستگی‌های آسیب‌پذیر

dotnet list package --vulnerable --include-transitive
npm audit

فریم‌ورک‌ها و پکیج‌ها را به‌روز نگه دارید، Dependabot یا Renovate را فعال کنید و پکیج‌های بلااستفاده را حذف کنید.

۹. ضعف در احراز هویت

  • Endpointهای ورود، کد یکبارمصرف و بازیابی رمز را محدودیت نرخ بزنید تا جلوی Brute Force گرفته شود.
  • بعد از تلاش‌های ناموفق مکرر، حساب را موقتاً قفل یا کند کنید.
  • از توکن دسترسی کوتاه‌عمر و چرخش Refresh Token استفاده کنید.
  • برای حساب‌های مدیر احراز هویت چندعاملی (MFA) داشته باشید.
  • برای «کاربر ناشناخته» و «رمز اشتباه» پیام یکسان برگردانید.

۱۰. ریسک‌های آپلود فایل

// ✅ مدیریت دفاعی آپلود
var allowed = new[] { ".jpg", ".jpeg", ".png", ".webp" };
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!allowed.Contains(ext) || file.Length > 5 * 1024 * 1024)
    return BadRequest("Invalid file.");

var safeName = $"{Guid.NewGuid():N}{ext}";        // به نام فایل کلاینت اعتماد نکنید
var path = Path.Combine(uploadsRoot, safeName);    // بیرون از پوشه‌های اجرایی

اندازه و نوع فایل را بررسی کنید (ترجیحاً با بررسی محتوا، نه فقط پسوند)، فایل‌ها را بیرون از مسیرهای اجرایی ذخیره کنید، با Content-Type درست ارائه دهید و برای حذف Payload مخرب، کدگذاری مجدد تصاویر را در نظر بگیرید.

لاگ و مانیتورینگ

به حمله‌ای که نمی‌بینید نمی‌توانید واکنش نشان دهید. شکست‌های احراز هویت، ردهای مجوزدهی و خطاهای اعتبارسنجی را لاگ کنید؛ اما هرگز رمز عبور، توکن یا شماره کارت کامل را لاگ نکنید. برای افزایش‌های غیرعادی هشدار تنظیم کنید.

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

  • هر Endpoint مجوزدهی صریح و بررسی مالکیت دارد
  • همه‌ی دسترسی‌ها به دیتابیس پارامتری هستند
  • هیچ HTML خامی بدون پاک‌سازی رندر نمی‌شود؛ CSP تنظیم شده است
  • HTTPS و HSTS؛ کوکی‌های Secure، HttpOnly و SameSite
  • رمزهای عبور با الگوریتم کند هش شده‌اند
  • کلیدها در Vault یا متغیر محیطی هستند، نه در Git یا کد فرانت‌اند
  • CORS محدود است؛ صفحات خطای Production عمومی‌اند
  • Endpointهای احراز هویت محدودیت نرخ دارند
  • وابستگی‌ها مرتب اسکن می‌شوند
  • آپلودها اعتبارسنجی و امن ذخیره می‌شوند

جمع‌بندی

بیشتر آسیب‌پذیری‌ها از اعتماد به ورودی و فراموش کردن مجوزدهی می‌آیند. همه‌ی کوئری‌ها را پارامتری کنید، خروجی را Encode و پاک‌سازی کنید، مالکیت هر منبع را در سرور بررسی کنید، کلیدها را بیرون از کد نگه دارید، HTTPS و CORS و هدرها را با دقت پیکربندی کنید و وابستگی‌ها را به‌روز نگه دارید. این عادت‌ها را بخشی از کدنویسی روزانه و بازبینی کد کنید تا برنامه‌هایتان در برابر اکثر حملات واقعی مقاوم باشند.

پرسش‌های متداول

آیا React خودبه‌خود در برابر XSS امن است؟

تا حد زیادی، چون JSX مقادیر را Escape می‌کند. خطرها dangerouslySetInnerHTML، آدرس‌های تحت کنترل کاربر و اسکریپت‌های جانبی هستند.

آیا استفاده از EF Core جلوی SQL Injection را می‌گیرد؟

کوئری‌های LINQ و FromSql با Interpolation پارامتری هستند. چسباندن رشته در FromSqlRaw یا ExecuteSqlRaw خطر را برمی‌گرداند.

برای امن کردن یک برنامه‌ی موجود از کجا شروع کنم؟

از کنترل دسترسی (بررسی مالکیت در همه‌ی Endpointها)، مدیریت کلیدها و به‌روزرسانی وابستگی‌ها شروع کنید؛ سریع‌ترین کاهش ریسک را می‌دهند.