Nginx+Lua设置自动黑名单

Nginx+Lua设置自动黑名单




由于服务器最近遭受DOS攻击,笔者想到为自己的服务器设置自动黑名单屏蔽功能,实现方式如下:

1 安装OpenResty

由于需要使用lua语言处理nginx的HTTP链接,因此需要安装OpenResty,编译安装可从官网下载,然后执行以下程序进行编译安装:

./configure --prefix=/usr/local/openresty --user=www --group=www --error-log-path=/www/log/nginx-error.log --conf-path=/www/server/nginx/nginx.conf --with-http_stub_status_module
gmake
gmake install

2 配置环境

由于笔者之前使用的是原生nginx,因此需要重新配置环境,首先创建并打开/usr/lib/systemd/system/openresty.service文件,编辑后保存文件为以下内容:

[Unit]
Description=OpenResty Nginx Web Server
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/www/server/nginx/nginx.pid
ExecStart=/usr/local/openresty/bin/openresty
ExecReload=/usr/local/openresty/bin/openresty -s reload
ExecStop=/usr/local/openresty/bin/openresty -s stop
PrivateTmp=true

[Install]
WantedBy=multi-user.target

使用如下命令停止并删除原有的nginx:

systemctl disable nginx
rm -rf /usr/lib/systemd/system/nginx.service

最后,我们还需要改变环境的PATH变量,打开\etc\profile文件后,可见文件最后对环境变量的默认设置为:

export MYSQL_HOME=/usr/local/mysql
export NGINX_HOME=/usr/local/nginx
export PHP_HOME=/usr/local/php
export REDIS_HOME=/usr/local/redis
export ZABBIX_HOME=/usr/local/zabbix
export POSTFIX_HOME=/usr/local/postfix
export PATH=$PATH:$MYSQL_HOME/bin:$NGINX_HOME/sbin:$PHP_HOME/bin:$PHP_HOME/sbin:$REDIS_HOME/bin:$ZABBIX_HOME/bin:$ZABBIX_HOME/sbin:$POSTFIX_HOME/usr/bin:$POSTFIX_HOME/usr/sbin:$POSTFIX_HOME/libexec/postfix

将文件修改为以下内容:

export MYSQL_HOME=/usr/local/mysql
export OPENRESTY_HOME=/usr/local/openresty
export PHP_HOME=/usr/local/php
export REDIS_HOME=/usr/local/redis
export ZABBIX_HOME=/usr/local/zabbix
export POSTFIX_HOME=/usr/local/postfix
export PATH=$PATH:$MYSQL_HOME/bin:$OPENRESTY_HOME/bin:$PHP_HOME/bin:$PHP_HOME/sbin:$REDIS_HOME/bin:$ZABBIX_HOME/bin:$ZABBIX_HOME/sbin:$POSTFIX_HOME/usr/bin:$POSTFIX_HOME/usr/sbin:$POSTFIX_HOME/libexec/postfix

3 自动黑名单程序设置

我们需要写lua脚本,来完成自动黑名单的相关功能,我们需要下了解具体功能的流程图:

具体程序代码如下:

local redis_service = ngx.var.redis_service

local redis_port = tonumber(ngx.var.redis_port)

local redis_db = tonumber(ngx.var.redis_db)

local black_count = tonumber(ngx.var.black_count)

local black_rule_unit_time = tonumber(ngx.var.black_rule_unit_time)

local cache_ttl = tonumber(ngx.var.black_ttl)

local remote_ip = ngx.var.remote_addr

local redis_connect_timeout = 60

local redis = require "resty.redis"

local Redis = redis:new()

local auto_blacklist_key = ngx.var.auto_blacklist_key

Redis:set_timeout(redis_connect_timeout)

local RedisConnectOk,ReidsConnectErr = Redis:connect(redis_service,redis_port)

if not RedisConnectOk then
    ngx.log(ngx.ERR,"ip_blacklist connect Redis Error :" .. ReidsConnectErr)
else
    Redis:select(redis_db)
    local key = auto_blacklist_key..":"..remote_ip
    local Status = Redis:get(key)
    if Status ~= ngx.null then
        if Status == "Connect" then
            local key_connect_count = auto_blacklist_key..":key_connect_count:"..remote_ip
            local count = Redis:get(key_connect_count)
            if count ~= ngx.null then
                if tonumber(count) > black_count then
                    Redis:del(key_connect_count)
                    Redis:set(key,"Black")
                    Redis:expire(key,cache_ttl)
                else
                    Redis:set(key_connect_count,tonumber(count)+1)
                    Redis:expire(key_connect_count,black_rule_unit_time)
                end
            end
        elseif Status == "Black" then
            ngx.log(ngx.ERR,"The visit is blocked by the blacklist because it is too frequent. Please visit later.")
            return ngx.exit(ngx.HTTP_FORBIDDEN)
        end
    else
        local key_connect_count = auto_blacklist_key..":key_connect_count:"..remote_ip
        local count = Redis:get(key_connect_count)
        if count == ngx.null then
            Redis:del(key_connect_count)
        end
        Redis:set(key,"Connect")
        Redis:set(key_connect_count,1)
        Redis:expire(key,black_rule_unit_time)
        Redis:expire(key_connect_count,black_rule_unit_time)
    end
end

4 修改网页的vhost配置

程序写好了,还需要Nginx引擎去读,让这段脚本生效,因此,我们需创建一个/www/server/nginx/lua并创建一个/www/server/nginx/lua/ip_blacklist.lua,创建文件的命令如下:

mkdir /www/server/nginx/lua
vi ip_blacklist.lua

输入以上程序后保存文件,然后输入vi /www/server/nginx/blacklist_params指令创建blacklist_params文件,并输入以下内容:

set $redis_service "127.0.0.1";
set $redis_port 6379;
set $redis_db 0;
set $black_count 300;
set $black_rule_unit_time 1;
set $black_ttl 3600;

然后可以执行vi /www/server/nginx/vhost/qhjack.conf打开/www/server/nginx/vhost/qhjack.conf文件,可见文件内容如下:

server {
    listen 80 default_server;
    server_name qhjack.cn www.qhjack.cn;
    root /www/root/qhjack;
    index index.php index.html;
    limit_req zone=server burst=20 nodelay;
    limit_req_status 503;
    limit_req_log_level error;
    limit_rate 100k;
    location ~ \.php$ {
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root/$fastcgi_script_name;
        include        fastcgi_params;
    }
    charset utf-8;
    error_log /www/log/qhjack/error.log;
    access_log /www/log/qhjack/access.log;
    location / {
        if (-f $request_filename/index.html){
            rewrite (.*) $1/index.html break;
        }
        if (-f $request_filename/index.php){
            rewrite (.*) $1/index.php;
        }
        if (!-f $request_filename){
            rewrite (.*) /index.php;
        }
    }

    location ~* \.(jpg|jpeg|gif|png|ico|swf)$ {
        expires 3y;
        access_log off;
        gzip off;
    }

    location /robots.txt$ {
        allow all;
        log_not_found off;
        access_log off;
    }
}

修改为以下内容:

server {
    listen 80 default_server;
    server_name qhjack.cn www.qhjack.cn;
    root /www/root/qhjack;
    index index.php index.html;
    limit_req zone=server burst=20 nodelay;
    limit_req_status 503;
    limit_req_log_level error;
    limit_rate 100k;
    set $auto_blacklist_key "blacklist:qhjack";
    include blacklist_params;
    access_by_lua_file /www/server/nginx/lua/ip_blacklist.lua;
    location ~ \.php$ {
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root/$fastcgi_script_name;
        include        fastcgi_params;
    }
    charset utf-8;
    error_log /www/log/qhjack/error.log;
    access_log /www/log/qhjack/access.log;
    location / {
        if (-f $request_filename/index.html){
            rewrite (.*) $1/index.html break;
        }
        if (-f $request_filename/index.php){
            rewrite (.*) $1/index.php;
        }
        if (!-f $request_filename){
            rewrite (.*) /index.php;
        }
    }

    location ~* \.(jpg|jpeg|gif|png|ico|swf)$ {
        expires 3y;
        access_log off;
        gzip off;
    }

    location /robots.txt$ {
        allow all;
        log_not_found off;
        access_log off;
    }
}
打赏

7 thoughts on “Nginx+Lua设置自动黑名单

  1. 你好,这样做计数的话,还是会出现一个并发问题。在多次请求到服务器中时,count是相同的,请问应该如何解决?

    1. 我有点不太明白你的『多次请求到服务器,count相同』的具体意思,能否解释的更详细点,这个脚本只是一个简易的用于缓解服务器遭受恶意DOS/DDOS的脚本(仅缓解),只有当IP频繁请求超过阀值,lua脚本才会动作启动防御而返回拒绝服务,当然也许一个更彻底的系统全局防御方案是使用fail2ban来监测log来进行自动iptables屏蔽动作。

  2. 就是在取访问计数中,如果多个请求过来同时取,就会得到同样的数字,譬如都取得5,这样之后的count+1就没有意义了,因为始终都是6,所以我认为使用redis的INCR应该可以解决。

    这只是我的一个想法,如果有什么错误的话,希望可以帮忙解答一下。
    不知道为什么回复不了,所以发起个新的讨论。

    1. 没错,确实有这种情况,之前编写脚本的时候并没有考虑到这种情况,使用incr替代count+1,或许可以解决这件事。如果有兴趣的话,可以来我的qq详细讨论下。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

扫码二维码快速访问本页

Nginx+Lua设置自动黑名单 – 起航天空