天天看點

Dotnet Core Public API的安全實踐

公開API的安全,其實更重要。

一、API的安全

作為一個Dotnet Core的老司機,寫API時,能兼顧到API的安全,這是一種優雅。

通常,我們會用認證來保證API的安全,無敵的

Authorize

能解決我們很多的問題。

但是,總有一些場合,我們沒辦法用

Authorize

,而隻能用匿名或不加驗證的方式來通路。比方電商中查詢SKU的清單并在前端展示,通常這個無關使用者和權限,在完成API的時候,我們也不會加入認證

Authorize

這種情況下,如果直接寫,不加入安全級别,這樣的體系結構是有可能成為可供利用的安全漏洞的。

Dotnet Core架構已經提供了一些常見漏洞的解決方法,包括:

  • 跨站點腳本
  • SQL注入
  • 跨站點請求僞造(CSRF)
  • 重定向

等等。

但是,我們還需要更進一步,還需要照顧到以下常見的攻擊:

  • 拒絕服務(DOS)
  • 分布式拒絕服務(DDOS)
  • 批量API調用
  • 探測響應
  • 資料抓取

這部分内容,需要我們自己實作。當然,這部分内容的實作,也可以從Web Server上進行設定。

本文讨論的,是代碼的實作。

    為了防止不提供原網址的轉載,特在這裡加上原文連結:https://www.cnblogs.com/tiger-wang/p/13471718.html

二、相關代碼

今天偷個懶,不講原理,以分享代碼為主。

2.1 基于IP的用戶端請求限制

通過限制用戶端在指定的時間範圍内的請求數量,防止惡意bot攻擊。

代碼中,我建立了一個基于IP的請求限制過濾器。

注意:有多個用戶端位于同一個IP位址的情況,這個情況在這個代碼中沒有考慮。如果您希望實作這一點,可以把幾種方式結合起來使用。

以下是代碼:

[AttributeUsage(AttributeTargets.Method)]
public class RequestLimitAttribute : ActionFilterAttribute
{
    public string Name { get; }
    public int NoOfRequest { get; set; }
    public int Seconds { get; set; }

    private static MemoryCache Cache { get; } = new MemoryCache(new MemoryCacheOptions());

    public RequestLimitAttribute(string name, int noOfRequest = 5, int seconds = 10)
    {
        Name = name;
        NoOfRequest = noOfRequest;
        Seconds = seconds;
    }
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var ipAddress = context.HttpContext.Request.HttpContext.Connection.RemoteIpAddress;
        var memoryCacheKey = $"{Name}-{ipAddress}";

        Cache.TryGetValue(memoryCacheKey, out int prevReqCount);
        if (prevReqCount >= NoOfRequest)
        {
            context.Result = new ContentResult
            {
                Content = $"Request limit is exceeded. Try again in {Seconds} seconds.",
            };
            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.TooManyRequests;
        }
        else
        {
            var cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(Seconds));
            Cache.Set(memoryCacheKey, (prevReqCount + 1), cacheEntryOptions);
        }
    }
}
           

使用時,隻要在需要的API前加屬性即可:

[HttpGet]
[RequestLimit("DataGet", 5, 30)]
public IEnumerable<WeatherForecast> Get()
{
    ...
}
           

2.2 引用頭檢查

對API請求的請求引用頭進行檢查,可以防止API濫用,以及跨站點請求僞造(CSRF)攻擊。

同樣,也是采用自定義屬性的方式。

public class ValidateReferrerAttribute : ActionFilterAttribute
{
    private IConfiguration _configuration;

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        _configuration = (IConfiguration)context.HttpContext.RequestServices.GetService(typeof(IConfiguration));

        base.OnActionExecuting(context);

        if (!IsValidRequest(context.HttpContext.Request))
        {
            context.Result = new ContentResult
            {
                Content = $"Invalid referer header",
            };
            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.ExpectationFailed;
        }
    }
    private bool IsValidRequest(HttpRequest request)
    {
        string referrerURL = "";

        if (request.Headers.ContainsKey("Referer"))
        {
            referrerURL = request.Headers["Referer"];
        }
        if (string.IsNullOrWhiteSpace(referrerURL)) return true;

        var allowedUrls = _configuration.GetSection("CorsOrigin").Get<string[]>()?.Select(url => new Uri(url).Authority).ToList();

        bool isValidClient = allowedUrls.Contains(new Uri(referrerURL).Authority);

        return isValidClient;
    }
}
           

這裡我用了一個配置,在

appsetting.json

中:

{
  "CorsOrigin": ["https://test.com", "http://test1.cn:8080"]
}
           

CorsOrigin

參數中加入允許引用的來源域名:端口清單。

使用時,在需要的API前加屬性:

[HttpGet]
[ValidateReferrer]
public IEnumerable<WeatherForecast> Get()
{
    ...
}
           

2.3 DDOS攻擊檢查

DDOS攻擊在網上很常見,這種攻擊簡單有效,可以讓一個網站瞬間開始并長時間無法響應。通常來說,網站可以通過多種節流方法來避免這種情況。

下面我們換一種方式,用中間件

MiddleWare

來限制特定用戶端IP的請求數量。

public class DosAttackMiddleware
{
    private static Dictionary<string, short> _IpAdresses = new Dictionary<string, short>();
    private static Stack<string> _Banned = new Stack<string>();
    private static Timer _Timer = CreateTimer();
    private static Timer _BannedTimer = CreateBanningTimer();

    private const int BANNED_REQUESTS = 10;
    private const int REDUCTION_INTERVAL = 1000; // 1 second    
    private const int RELEASE_INTERVAL = 5 * 60 * 1000; // 5 minutes    
    private RequestDelegate _next;

    public DosAttackMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    public async Task InvokeAsync(HttpContext httpContext)
    {
        string ip = httpContext.Connection.RemoteIpAddress.ToString();

        if (_Banned.Contains(ip))
        {
            httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
        }

        CheckIpAddress(ip);

        await _next(httpContext);
    }

    private static void CheckIpAddress(string ip)
    {
        if (!_IpAdresses.ContainsKey(ip))
        {
            _IpAdresses[ip] = 1;
        }
        else if (_IpAdresses[ip] == BANNED_REQUESTS)
        {
            _Banned.Push(ip);
            _IpAdresses.Remove(ip);
        }
        else
        {
            _IpAdresses[ip]++;
        }
    }


    private static Timer CreateTimer()
    {
        Timer timer = GetTimer(REDUCTION_INTERVAL);
        timer.Elapsed += new ElapsedEventHandler(TimerElapsed);
        return timer;
    }

    private static Timer CreateBanningTimer()
    {
        Timer timer = GetTimer(RELEASE_INTERVAL);
        timer.Elapsed += delegate {
            if (_Banned.Any()) _Banned.Pop();
        };
        return timer;
    }

    private static Timer GetTimer(int interval)
    {
        Timer timer = new Timer();
        timer.Interval = interval;
        timer.Start();
        return timer;
    }

    private static void TimerElapsed(object sender, ElapsedEventArgs e)
    {
        foreach (string key in _IpAdresses.Keys.ToList())
        {
            _IpAdresses[key]--;
            if (_IpAdresses[key] == 0) _IpAdresses.Remove(key);
        }
    }
}
           

代碼中設定:1秒(1000ms)中有超過10次通路時,對應的IP會被禁用5分鐘。

使用時,在

Startup.cs

中直接加載中間件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...
    app.UseMiddleware<DosAttackMiddleware>();
    ...
}
           

三、結尾的話

以上代碼僅為抛磚引玉之用。

公開的API,未經驗證的API,在生産環境會因為種種原因被攻擊。這幾天公司的系統就因為這個出了大事。

是以,寫API的時候,要充分考慮到這些網絡攻擊的可能性,通過正确的處理,來防止來自網絡的攻擊。

這是一份責任,也是一個理念。

與大家共勉!

(全文完)

本文的代碼,我已經傳到Github上,位置在:https://github.com/humornif/Demo-Code/tree/master/0021/demo

Dotnet Core Public API的安全實踐

微信公衆号:老王Plus

掃描二維碼,關注個人公衆号,可以第一時間得到最新的個人文章和内容推送

本文版權歸作者所有,轉載請保留此聲明和原文連結

繼續閱讀