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
微信公众号:老王Plus 扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送 本文版权归作者所有,转载请保留此声明和原文链接 |