
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;
}
}
你好,这样做计数的话,还是会出现一个并发问题。在多次请求到服务器中时,count是相同的,请问应该如何解决?
我有点不太明白你的『多次请求到服务器,count相同』的具体意思,能否解释的更详细点,这个脚本只是一个简易的用于缓解服务器遭受恶意DOS/DDOS的脚本(仅缓解),只有当IP频繁请求超过阀值,lua脚本才会动作启动防御而返回拒绝服务,当然也许一个更彻底的系统全局防御方案是使用fail2ban来监测log来进行自动iptables屏蔽动作。
就是在取访问计数中,如果多个请求过来同时取,就会得到同样的数字,譬如都取得5,这样之后的count+1就没有意义了,因为始终都是6,所以我认为使用redis的INCR应该可以解决。
这只是我的一个想法,如果有什么错误的话,希望可以帮忙解答一下。
不知道为什么回复不了,所以发起个新的讨论。
没错,确实有这种情况,之前编写脚本的时候并没有考虑到这种情况,使用incr替代count+1,或许可以解决这件事。如果有兴趣的话,可以来我的qq详细讨论下。
你好,我有发起一个临时会话但是没回应,是右上方的qq号吗
是的 QQ 744439622
你好,我发起了一个临时会话,可是还没有回复,请问是右侧的QQ号吗?
是的 744439622 临时会话不行的话加qq也可以