nginx限流防刷方案

前言

互联网发展已经进入了存量期,一开始低廉的获客成本已经不复存在,互联网公司通过付出诱人而高昂的补贴以此来拉新的方式,催生了大量的黑产,灰产.并且越来越多的公司爆出数据泄露,暗网上用户的密码和隐私信息已经被打包明码标价出售.记得年前我司遭遇了撞库的攻击,部分用户的登陆凭证和密码被窃取.黑客通过其他渠道获得的手机号和密码字典,来请求我们的登陆接口.虽然最后造成的损失有限,但暴露了很多服务架构和业务安全性上的问题.

首先,签名算法暴露和弱密码的问题.验签算法暴露在目前反编译解包如此成熟的情况下,很难防止.目前想到方案就是控制签名的salt分发.弱密码需要从业务层改造,成本比较高.或者弱化用账号密码登陆,转为第三方授权.

其次,这次攻击发生的很早,但是几个小时后才通过接口流量监控发现请求量异常.发现时已经有大量的用户密码被拿到,造成后面的被动局面.如何才能发现这些异常的请求.

最后,通过分析日志,黑客请求的ip数量都集中在有限个ip,完全可以通过规则去拒绝这些非法的请求.

上述的后两个问题都可以通过今天要探讨的方案解决.

谁是狼人

服务器每天都在接受上亿次的请求,怎么样判别哪些请求是无辜的平民,哪些是请求是要你命的狼人.

通过分析请求log,可以发现非法的请求有几个特征:

1. 业务纬度的单一性:黑客攻击往往有目的,只会攻击那些比较重要的接口.比如上述案例里的登陆和校验手机号.
2. 时间纬度的集中性:为了短时间内拿到大量数据,黑客会通过批量频繁的请求.
3. ip有限性:这种窃取数据型的攻击行为有别于DDOS,一般ip代理池的数量不会太过庞大,每个ip请求的次数都比较多

因此,采用{uri}+{ip}作为key,在某个时间纬度上,去统计请求量,从而鉴别非法请求的方法,能够有效的抵御案例的攻击

谁是猎人

那么问题来了,谁来当猎人,抵挡狼人的攻击呢.

服务架构上无非就几个地方,lb,nginx,server.个人觉得放在哪层区别不大,遵循非法请求尽早丢弃的原则和考虑改造的难度,我选择了在nginx层.

nginx魔法

作为nginx的菜鸡选手,又因nginx版本比较低,当时配置规则遇到了很多问题.所幸最后摸索出可行的方式.主要用到nginx的geo,map,limit_req

  • 规则配置
        ##IP白名单
        geo $whiteiplist {
            default 1;
            127.0.0.1 0;
            10.0.0.0/8 0;
  
        }
        map $whiteiplist $limit {
            1 $limit_key;
            0 "";
        }
                ## 接口白名单
       	map $uri $limit2{
                        default $limit;
                        /api/sample "";

                }
        limit_req_status 406;

                ### 频率控制
        limit_req_zone $limit2 zone=freq_controll:100m rate=10r/s;
        limit_req_zone $limit2 zone=freq_controll_2:100m rate=500r/m;


这段规则写的比较繁琐 主要是受限于nginx版本较低 limit_req_zone 这里不支持两个变量,以及要支持接口白名单和ip白名单

    location / {
	limit_req zone=freq_controll burst=5 nodelay;
	limit_req zone=freq_controll_2 burst=10 nodelay;
	error_page 406 =406  @f406;
	location @f406 {
		access_log syslog:server=127.0.0.1:12301;
		return 406;
		}
	}

注意到limit_req中两个奇怪的参数 burst ,nodelay 开始的时候我也感到疑惑,了解了背后的逻辑和算法,才理解了其中的奥义.

流量控制

流量控制的算法中常见的有漏斗算法,有两种思路:1:当水的量已经达到桶的上限时,先暂时停止接收,等到桶的水漏了一部分后再继续接收.2.超出桶的流量直接丢弃.总的来说,两种思路无非就是,等待和丢弃.

而limit_req 模块的算法又与漏斗算法有些不同,属于令牌桶算法.两者最大的区别在于,漏斗算法只能强制性限制流量的大小,没有办法应对某些突发的负载.而令牌桶恰恰弥补了这个不足.

令牌桶的核心有几个:

1.定义一个有个数上限的令牌桶,以固定速率投放令牌到桶里. 

2.当桶里有n个令牌,则处理n个请求.并且消耗掉相应的令牌. 

而这时候burst参数就发挥了作用.假设一秒内同时有120个请求发到服务器.按照传统的漏斗算法,多出的这20个请求会被直接拒绝 或者是放到队列中等待.而在令牌桶算法中又是另外一种景象了.令牌桶中实际上有100个令牌.但是允许并发10个请求.那么多出来10个请求会被拒绝 .

在没有配置nodelay的情况下,这10个请求会被放到队列.以0.001秒的速率被取出,共计消耗0.1秒.处理110个请求用了1.1秒.实际上这个等待是没有必要的.

而配置了nodelay,这多出的十个请求会被正常处理,只是burst的数量会被清空.等待令牌重新补充,才会重新接收请求.处理110个请求用了1秒.但上面的情况一样,都要等令牌补充才能接收请求.


版权声明:本文为qq_43134606原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。