zoukankan      html  css  js  c++  java
  • DotNet Core 公共API的安全

    1、API的安全

    在写API的时候,能兼顾到API的安全,这是一种代码的优雅。

    通常,我们会用认证来保证API的安全,无敌的Authorize能解决我们很多的问题。

    但是,总有一些场合,我们没办法用Authorize,而只能用匿名或不加验证的方式来访问。比方电商中查询SKU的列表并在前端展示,通常这个无关用户和权限,在完成API的时候,我们也不会加入认证Authorize

    这种情况下,如果直接写,不加入安全级别,这样的体系结构是有可能成为可供利用的安全漏洞的。

    Dotnet Core框架已经提供了一些常见漏洞的解决方法,包括:

      1、跨站点脚本

      2、SQL注入

      3、跨站点请求伪造(CSRF)

      4、重定向

      ......

    但是,我们还需要更进一步,还需要照顾到以下常见的攻击:

      1、拒绝服务(DOS)

      2、分布式拒绝服务(DDOS)

      3、批量API调用

      4、探测响应

      5、数据抓取

    这部分内容,需要我们自己实现。当然,这部分内容的实现,也可以从Web Server上进行设置。

    本文讨论的,是代码的实现。

    2、相关代码

    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>();
        ...
    }

    3、总结

    公开的API,未经验证的API,在生产环境会因为种种原因被攻击,如果涉及到金钱,将会发生意想不到的损失。所以,写API的时候,要充分考虑到这些网络攻击的可能性,通过正确的处理,来防止来自网络的攻击。对自己写的代码应该需要适当的洁癖。这是一份责任,也是一个理念。

  • 相关阅读:
    Android webview 应用
    Android 访问权限设置
    Android应用----如何让应用全屏
    PHP基础
    递归在PHP中的应用举例
    软工实践个人总结
    2020软件工程实践第2次结对编程作业
    2020软件工程第一次结对作业
    2020软件工程实践第一次个人编程作业
    A brief introduction of myself
  • 原文地址:https://www.cnblogs.com/taotaozhuanyong/p/13498158.html
Copyright © 2011-2022 走看看