openresty|redis拦截高频访问IP

cera cera

CC攻击

网站受到攻击通常是黑客通过几个甚至多个IP地址,在短时间内进行超高频率访问,从而让服务器在短时间内增加巨大的计算量,导致负载增加,降低响应能力,甚至直接宕机停止服务。
通常这类情况我们只能通过查看分析网站日志,从而获得攻击者的IP地址,再通过防火墙进行拦截。
但一般而言这只会发生在监控系统已经开始报警之后,也就是网站或服务已经遭受到了攻击,并造成影响之后。并且在日志中搜寻到攻击者的IP并不是十分简单的事情,通常当我们找到了攻击者,攻击者可能已经停止了攻击,我们也只能预防他下次可能的攻击。

自动拦截

经历了黑客们深夜的骚扰和攻击,如何让那些短时间内大量访问的地址被自动拦截变成了努力的方向。
云服务商提供了WAF等商业化产品,协助我们处理这些威胁。
相比较于这些高昂价格的产品,开源软件同样在灵活性和可整合性上有很大的优势,接下来就介绍一下我是如何使用openrestyredis实现拦截高频访问的地址。

安装环境

之前的文章已经介绍过: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断开重连后,每次访问都要建立连接问题。

cera cloudiplc tengxunyun

相关推荐