openresty+redis拦截高频访问IP
CC攻击
网站受到攻击通常是黑客通过几个甚至多个IP地址,在短时间内进行超高频率访问,从而让服务器在短时间内增加巨大的计算量,导致负载增加,降低响应能力,甚至直接宕机停止服务。
通常这类情况我们只能通过查看分析网站日志,从而获得攻击者的IP地址,再通过防火墙进行拦截。
但一般而言这只会发生在监控系统已经开始报警之后,也就是网站或服务已经遭受到了攻击,并造成影响之后。并且在日志中搜寻到攻击者的IP并不是十分简单的事情,通常当我们找到了攻击者,攻击者可能已经停止了攻击,我们也只能预防他下次可能的攻击。
自动拦截
经历了黑客们深夜的骚扰和攻击,如何让那些短时间内大量访问的地址被自动拦截变成了努力的方向。
云服务商提供了WAF等商业化产品,协助我们处理这些威胁。
相比较于这些高昂价格的产品,开源软件同样在灵活性和可整合性上有很大的优势,接下来就介绍一下我是如何使用openresty和redis实现拦截高频访问的地址。
安装环境
之前的文章已经介绍过:Openresty+Redis 动态切换upstream (http://learn-learn.top/archives/169.html)
大致按照官方介绍就可以轻松安装。
nginx配置
nginx在初始化时建立一个redis的链接,并且在每次访问前需要执行block.lua进行验证
init_by_lua_block {
redis = require "redis"
client = redis.connect('127.0.0.1', 6379)
}
server {
listen 8080;
location / {
access_by_lua_file /usr/local/nginx/conf/lua/block.lua;
proxy_pass http://192.168.1.102:8000;
}
}
lua脚本:
function isConnected()
return client:ping()
end
function createRedisConnection()
return redis.connect('127.0.0.1', 6379)
end
if pcall(isConnected)then --如果发生redis连接失败,将停止拦截。
--
else
if pcall(createRedisConnection)then --断开重连会发送每次访问都需要重连redis
client = createRedisConnection(); --如果访问量大的情况下,建议关闭重连,if pcall不执行,直接ngx.exit
else
ngx.exit(ngx.OK);
end
end
local ttl = 60; --监测周期
local bktimes = 30; --在监测周期内达到触发拦截的访问量
block_ttl = 600; --触发拦截后拦截时间
ip = ngx.var.remote_addr
ipvtimes = client:get(ip)
if(ipvtimes)then
if(ipvtimes == "-1")then
--ngx.say("blocked")
return ngx.exit(403);
else
last_ttl = client:ttl(ip)
--ngx.say("key exist.ttl is ",last_ttl);
if(last_ttl==-1)then
client:set(ip,0)
client:expire(ip,ttl)
--ngx.say("ttl & vtimes recount")
return ngx.exit(ngx.OK);
end
vtimes = tonumber(client:get(ip))+1;
if(vtimes<bktimes)then
client:set(ip,vtimes);
client:expire(ip,last_ttl)
--ngx.say(ip," view ",vtimes," times");
return ngx.exit(ngx.OK);
else
--ngx.say(ip," will be block noext time.")
client:set(ip,-1);
client:expire(ip,block_ttl)
return ngx.exit(ngx.OK);
end
end
else
--ngx.say("key do not exist")
client:set(ip,1)
--ngx.say(ip," view 1 times")
client:expire(ip,ttl)
return ngx.exit(ngx.OK)
end
脚本说明:
1.重要参数:
ttl = 60;
–监测周期
bktimes = 30;
–在监测周期内达到触发拦截的访问量
block_ttl = 600;
–触发拦截后拦截时间
以上参数表示,一个IP地址在60秒内访问超过30次将被拦截600秒。
2.逻辑说明:
a)检测初始化的redis连接是否能够正常运行,如果连接失败或已经断开,将会重新建立连接,如果仍旧无法连接,将直接放行。这里是为了避免redis宕机导致nginx无法正常响应。当然如果初始连接中断,将会导致每次访问都会创建redis连接。
b)当某个IP首次访问时,将在redis中新建一个以IP地址为KEY的键(如果需要多个站点,修改下key的命名规则即可),value为1,并设置expire时间。当这个地址再次访问且key尚未过期前,将会每次递增key的value数,直到到达达到bktimes,或者key到期从而消亡。(本人用的redis5.0,到期key会直接不存在,可能部分版本到期后value为-1)
c)当key过期后,对于系统而言就是第一次访问,重新创建value为1的新key
d)当达到bktimes后,会将对应的IP的key的value设置为-1,且过期时间为block_ttl。
e)当访问到value为-1的key,即某个IP达到了我们设定的访问频次,我们将直接拦截,返回403.
3.完善方向:
a)在访问前添加黑白名单功能(这个在redis中新建立两个key即可)
b)拦截IP段(根据访问的IP地址建立以IP段为key的字段即可)
c)redis断开重连后,每次访问都要建立连接问题。