Dotnet Core Public API的安全實(shí)踐
公開API的安全,其實(shí)更重要。
?
一、API的安全
作為一個(gè)Dotnet Core的老司機(jī),寫API時(shí),能兼顧到API的安全,這是一種優(yōu)雅。
?
通常,我們會(huì)用認(rèn)證來保證API的安全,無敵的Authorize能解決我們很多的問題。
但是,總有一些場(chǎng)合,我們沒辦法用Authorize,而只能用匿名或不加驗(yàn)證的方式來訪問。比方電商中查詢SKU的列表并在前端展示,通常這個(gè)無關(guān)用戶和權(quán)限,在完成API的時(shí)候,我們也不會(huì)加入認(rèn)證Authorize。
這種情況下,如果直接寫,不加入安全級(jí)別,這樣的體系結(jié)構(gòu)是有可能成為可供利用的安全漏洞的。
?
Dotnet Core框架已經(jīng)提供了一些常見漏洞的解決方法,包括:
跨站點(diǎn)腳本
SQL注入
跨站點(diǎn)請(qǐng)求偽造(CSRF)
重定向
等等。
但是,我們還需要更進(jìn)一步,還需要照顧到以下常見的攻擊:
拒絕服務(wù)(DOS)
分布式拒絕服務(wù)(DDOS)
批量API調(diào)用
探測(cè)響應(yīng)
數(shù)據(jù)抓取
這部分內(nèi)容,需要我們自己實(shí)現(xiàn)。當(dāng)然,這部分內(nèi)容的實(shí)現(xiàn),也可以從Web Server上進(jìn)行設(shè)置。
本文討論的,是代碼的實(shí)現(xiàn)。
二、相關(guān)代碼
今天偷個(gè)懶,不講原理,以分享代碼為主。
2.1 基于IP的客戶端請(qǐng)求限制
通過限制客戶端在指定的時(shí)間范圍內(nèi)的請(qǐng)求數(shù)量,防止惡意bot攻擊。
代碼中,我建立了一個(gè)基于IP的請(qǐng)求限制過濾器。
注意:有多個(gè)客戶端位于同一個(gè)IP地址的情況,這個(gè)情況在這個(gè)代碼中沒有考慮。如果您希望實(shí)現(xiàn)這一點(diǎn),可以把幾種方式結(jié)合起來使用。
以下是代碼:
[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);
????????}
????}
}
使用時(shí),只要在需要的API前加屬性即可:
[HttpGet]
[RequestLimit("DataGet",?5,?30)]
public?IEnumerable?Get()
{
????...
}
2.2 引用頭檢查
對(duì)API請(qǐng)求的請(qǐng)求引用頭進(jìn)行檢查,可以防止API濫用,以及跨站點(diǎn)請(qǐng)求偽造(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;
????}
}
這里我用了一個(gè)配置,在appsetting.json中:
{
??"CorsOrigin":?["https://test.com",?"http://test1.cn:8080"]
}
CorsOrigin參數(shù)中加入允許引用的來源域名:端口列表。
使用時(shí),在需要的API前加屬性:
[HttpGet]
[ValidateReferrer]
public?IEnumerable?Get()
{
????...
}
2.3 DDOS攻擊檢查
DDOS攻擊在網(wǎng)上很常見,這種攻擊簡(jiǎn)單有效,可以讓一個(gè)網(wǎng)站瞬間開始并長(zhǎng)時(shí)間無法響應(yīng)。通常來說,網(wǎng)站可以通過多種節(jié)流方法來避免這種情況。
下面我們換一種方式,用中間件MiddleWare來限制特定客戶端IP的請(qǐng)求數(shù)量。
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);
????????}
????}
}
代碼中設(shè)置:1秒(1000ms)中有超過10次訪問時(shí),對(duì)應(yīng)的IP會(huì)被禁用5分鐘。
使用時(shí),在Startup.cs中直接加載中間件:
public?void?Configure(IApplicationBuilder?app,?IWebHostEnvironment?env)
{
????...
????app.UseMiddleware();
????...
}
三、結(jié)尾的話
以上代碼僅為拋磚引玉之用。
公開的API,未經(jīng)驗(yàn)證的API,在生產(chǎn)環(huán)境會(huì)因?yàn)榉N種原因被攻擊。這幾天公司的系統(tǒng)就因?yàn)檫@個(gè)出了大事。
所以,寫API的時(shí)候,要充分考慮到這些網(wǎng)絡(luò)攻擊的可能性,通過正確的處理,來防止來自網(wǎng)絡(luò)的攻擊。
這是一份責(zé)任,也是一個(gè)理念。
與大家共勉!
?
(全文完)
?
本文的代碼,我已經(jīng)傳到Github上,位置在:https://github.com/humornif/Demo-Code/tree/master/0021/demo
喜歡就來個(gè)三連,讓更多人因你而受益
