基於 .NETCore提供公共WebAPI的安全實踐
公開API的安全,其實更重要。
一、API的安全
作為一個Dotnet Core的老司機,寫API時,能兼顧到API的安全,這是一種優雅。
通常,我們會用認證來保證API的安全,無敵的 Authorize
能解決我們很多的問題。
但是,總有一些場合,我們沒辦法用 Authorize
,而只能用匿名或不加驗證的方式來訪問。比方電商中查詢SKU的列表並在前端展示,通常這個無關使用者和許可權,在完成API的時候,我們也不會加入認證 Authorize
。
這種情況下,如果直接寫,不加入安全級別,這樣的體系結構是有可能成為可供利用的安全漏洞的。
Dotnet Core框架已經提供了一些常見漏洞的解決方法,包括:
-
跨站點指令碼
-
SQL注入
-
跨站點請求偽造(CSRF)
-
重定向
等等。
但是,我們還需要更進一步,還需要照顧到以下常見的攻擊:
-
拒絕服務(DOS)
-
分散式拒絕服務(DDOS)
-
批量API呼叫
-
探測響應
-
資料抓取
這部分內容,需要我們自己實現。當然,這部分內容的實現,也可以從Web Server上進行設定。
本文討論的,是程式碼的實現。
二、相關程式碼
今天偷個懶,不講原理,以分享程式碼為主。
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": ["http://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上,位置在:http://github.com/humornif/Demo-Code/tree/master/0021/demo
喜歡就來個三連,讓更多人因你而受益
- .NET基礎知識快速通關(9)
- .NET基礎知識快速通關(6)
- .NET基礎知識快速通關(5)
- 面試寶典之.NET基礎知識快速通關(1)
- 填坑 | .NET 在Docker中訪問MSSQL報錯
- 如何分析.NET Core HttpClient請求異常
- 理解C#泛型原理
- 萬字長文講解:什麼是「抽象」?
- 簡述使用REST API 的最佳實踐
- 如何基於.NET Core構建分散式檔案儲存系統?
- 利用SOS擴充套件庫進入高階.NET6程式的除錯
- 淺議開發者面臨的資訊偏差影響因素
- 【新書速遞】龍芯開源LoongArch版,學會造計算機!
- .NET誕生20週年 .NET 7有什麼新東西?
- 基於.NET 製作一個氣象站 IoT 應用
- [探索 .NET 6]02 比較 WebApplicationBuilder 和 Host
- [探索 .NET 6]01 揭開 ConfigurationManager 的面紗
- 基於REACT和.NET CORE整合WINDOWS身份驗證
- 驚爆:Alexa 全球排名網站即將關閉
- 面試必備之C#10語法特性總結