آسیب پذیری پنهان: وقتی اندپوینت های Lookup داده های با ارزش شما را حراج می کنند!

آسیب پذیری پنهان: وقتی اندپوینت های Lookup داده های با ارزش شما را حراج می کنند!


آیا این سناریو را تجربه کرده اید؟ در فرم های کاربری، فیلترهای جستجو، و صفحات ثبت نام، نیازمند داده های پایه ای باشید؛ از لیست کشورها و شهرها گرفته تا عناوین دوره های آموزشی، عناوین مشاغل.

در نتیجه، در عرض چند دقیقه چنین کنترلری در پروژه متولد می شود:

GET /api/lookups/countries
GET /api/lookups/provinces
GET /api/lookups/cities
GET /api/lookups/course-educations
GET /api/lookups/genders

این اندپوینت ها در ظاهر کاملاً بی خطر، خواندنی (Read-Only) و عمومی به نظر می رسند. اما آیا تا به حال فکر کرده اید که یک اسکریپت ساده با چند خط کد پایتون می تواند در کسری از ثانیه ساختار کامل کسب و کار یا پایگاه داده جغرافیایی و دسته بندی های اختصاصی شما را Scrape کند؟

در این مقاله، ابتدا ریشه های معماری این آسیب پذیری را بررسی می کنیم و سپس یک استراتژی گام به گام و علمی برای محافظت از داده های پایه در ASP.NET Core پیاده سازی خواهیم کرد.

1. کالبدشکافی مشکل: چرا اندپوینت های Lookup آسیب پذیرند؟

بسیاری از توسعه دهندگان بر این باورند که "این ها صرفاً یک سری داده عمومی هستند، چه اهمیتی دارد؟" اما مشکل از چند زاویه قابل بررسی است:

  1. سرقت ارزش اختصاصی کسب وکار (Domain IP): شاید لیست کشورها ارزش محرمانگی نداشته باشد، اما لیست اختصاصی دوره ها، مهارت ها یا دسته بندی های خدماتی که با ماه ها تحقیق بازار جمع آوری کرده اید، دارایی سازمان شماست. رقبا می توانند تمام درختواره طبقه بندی شما را بدون زحمت کپی کنند.
  2. شمارش پایگاه داده (Database Enumeration): در اکثر پروژه ها، این خروجی ها شامل کلیدهای اصلی عددی صعودی (Id: 1, 2, 3...) هستند. این ساختار سرنخ های عمیقی درباره حجم داده ها، ساختار روابط کلید خارجی و معماری پایگاه داده به مهاجم می دهد.
  3. حملات منع سرویس (DDoS / Resource Exhaustion): فراخوانی هم زمان اندپوینتی که ۱۰,۰۰۰ رکورد (مثلاً تمام روستاها و شهرهای یک کشور) را بدون فیلتر از پایگاه داده بارگذاری و سریالایز می کند، منابع RAM و CPU سرور را به شدت درگیر می کند.

2. قانون طلایی کلاینت-سرور: توهم «امنیت ۱۰۰ درصدی داده های نمایشی»

پیش از ورود به کد، باید یک اصل تغییرناپذیر در معماری نرم افزار را بپذیریم:

«هر داده ای که برای رندر شدن به مرورگر کاربر ارسال می شود، توسط کاربر قابل خواندن است.»

هدف ما غیرممکن کردن مطلق واکشی داده نیست (چرا که کاربر عادی هم باید آن را ببیند)، بلکه بالا بردن هزینه استخراج خودکار (Scraping Cost) به حدی است که استخراج انبوه داده از صرفه اقتصادی و زمانی ساقط شود.

3. اصلاح معماری و پیاده سازی عملی در ASP.NET Core

برای دستیابی به معماری امن، باید یک سیستم دفاع لایه ای (Defense-in-Depth) طراحی کنیم.

گام اول: اصلاح طراحی API و حذف «استخراج یک باره» (Chunking & Hierarchy)

هیچ اندپوینتی نباید اجازه دهد کل اقیانوس داده ها در یک درخواست صید شود. داده ها باید ساختاریافته و وابسته باشند:

  • وابستگی سلسله مراتبی: استخراج شهرها باید مستلزم داشتن شناسه استان باشد.
  • اجباری کردن جستجو (Query-based Fetching): به جای بازگرداندن تمام دوره های آموزشی، کلاینت موظف است حداقل ۳ کاراکتر تایپ کند تا نتایج در سقف محدود (مثلاً حداکثر ۱۰ مورد) بازگردانده شود.
[HttpGet("cities")]
public async Task<IActionResult> GetCities(
    [FromQuery] int provinceId,
    [FromQuery] string? search = null,
    CancellationToken ct = default)
{
    if (provinceId <= 0)
        return BadRequest("شناسه استان الزامی است.");

    var query = _dbContext.Cities
        .AsNoTracking()
        .Where(c => c.ProvinceId == provinceId);

    if (!string.IsNullOrWhiteSpace(search))
    {
        query = query.Where(c => c.Name.Contains(search.Trim()));
    }

    var result = await query
        .Take(20) // سد بازدارنده در برابر استخراج انبوه
        .Select(c => new LookupDto(c.PublicUid, c.Name))
        .ToListAsync(ct);

    return Ok(result);
}

نکته امنیتی: به جای انتشار Id افزایشی دیتابیس، از یک Guid یا NanoId عمومی (PublicUid) استفاده کنید تا جلوی حملات حدس زدن ID (Insecure Direct Object References) گرفته شود.

گام دوم: انتقال داده های استاتیک کم تغییر به فرانت اند

چرا باید برای گزینه های ثابت مانند Gender، Salutation (آقا/خانم) یا وضعیت های تاهل اندپوینت بسازیم؟

این داده ها ماهیت استاتیک دارند. بهترین الگو، نگهداری آن ها به عنوان Enum در کلاینت (TypeScript/Dart) است. این کار هم بار سرور را کاهش می دهد و هم APIهای بدون ضرورت را حذف می کند.

گام سوم: سد دفاعی نرخ درخواست با Native Rate Limiting در .NET

از .NET 7 به بعد، مایکروسافت سیستم قدرتمند Rate Limiter را در هسته فریم ورک تعبیه کرده است. با این قابلیت، اسکریپت های اتوماسیون پس از چند درخواست متوقف می شوند:

در فایل Program.cs:

using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRateLimiter(options =>
{
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

    // اعمال محدودیت به ازای هر IP
    options.AddPolicy("LookupRatePolicy", httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 15, // حداکثر ۱۵ درخواست
                Window = TimeSpan.FromMinutes(1), // در هر دقیقه
                QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
                QueueLimit = 0
            }));
});

var app = builder.Build();

app.UseRateLimiter();

و روی کنترلر Lookup:

  [ApiController]
[Route("api/[controller]")]
[EnableRateLimiting("LookupRatePolicy")]
public class LookupsController : ControllerBase
{
    // ...
}

گام چهارم: توکن کوتاه مدت نشست و اعتبارسنجی فرانت اند (Contextual Token)

اگر دسترسی به فرم قبل از مرحله احراز هویت است (مثل صفحه ثبت نام)، برای اطمینان از این که درخواست مستقیماً از اپلیکیشن شما ارسال شده نه از اسکریپت پایتون یا Postman:

  1. هنگام لود اولیه صفحه فرم، یک توکن رمزنگاری شده کوتاه مدت (Short-lived Context Token) با طول عمر مثلاً ۵ دقیقه به کلاینت تحویل دهید.
  2. اندپوینت های Lookup را موظف کنید این توکن را در هدر X-Context-Token دریافت و اعتبارسنجی کنند.

برای لایه های حفاظتی حساس تر، تلفیق فرم با Cloudflare Turnstile یا سرویس های CAPTCHA هوشمند بدون اصطکاک (Invisible Captcha) مانع ورود ربات های بدون مرورگر واقعی خواهد شد.

۴. مقایسه قبل و بعد از اصلاح معماری

مشخصه معماری ناامن (سنتی) معماری مدرن و امن
روش واکشی لیست کامل بدون شرط (Fetch All) سلسله مراتبی و محدود (Chunked/Paginated)
شناسه رکورد کلیدهای اصلی افزایشی (Auto-increment ID) شناسه های عمومی محافظت شده (UUID / Public ID)
کنترل ترافیک نامحدود (آماده برای حمله DoS) محدود بر اساس نرخ با کد خطای 429
داده های ثابت تماس غیرضروری با API مدیریت در سطح کلاینت به عنوان متغیرهای ثابت
سهولت اسکرپ بسیار آسان (یک خط دستور curl) بسیار پرهزینه و همراه با سد امنیتی
سید حامد واحدی سید حامد واحدی     29 شهريور 1405