根据nginx日志自动封禁 IP

遇到的问题问题:

攻击者会每秒 POST 请求 /a/b.php 数次,拉黑后过一段时间会切到其他 IP 继续攻击,如果开始攻击大概 100 行内就可以找到这种日志,需要检测这种信息,几分钟执行一次并去拉黑

Tips:如果是大规模 ddos 或 cc 攻击的话,这是不顶用的,还是需要上 高防IP 或者 CDN 或者 高防流量包 以及 添加防火墙进行流量清洗 等。

思路一: iptables

  • 编写脚本,按日期拆分access.log
  • 编写定时任务,每天0点拆分访问日志
  • 编写脚本,分析access.log访问日志,封禁当天访问次数超过200的ip
  • 编写定时任务,每10分钟执行一次封禁ip脚本

这样做的比较好的一点是,让小黑子连 403 也看不到,高效简单

日志拆分脚本

LOG_PATHPID 换成你自己的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 每天0点执行日志按日期分隔脚本 0 0 * * * cd /www/Home/ && ./log_cut.sh
#!/bin/bash
#此脚本⽤于⾃动分割Nginx的⽇志,包括access.log
#每天00:00执⾏此脚本将前⼀天的access.log重命名为access-xxxx-xx-xx.log格式,并重新打开⽇志⽂件
#Nginx⽇志⽂件所在⽬录 todo 换成你自己的
LOG_PATH=/data/logs/nginx/

#获取昨天的⽇期
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)

#获取pid⽂件路径 todo 换成你自己的
PID=/var/run/nginx.pid

#分割⽇志
mv ${LOG_PATH}access.log ${LOG_PATH}access-${YESTERDAY}.log

#向Nginx主进程发送USR1信号,重新打开⽇志⽂件
kill -USR1 `cat ${PID}`

编写定时任务

1
2
3
4
crontab -e

# 每天0点执行日志按日期分隔脚本
0 0 * * * cd /www/Home/ && ./log_cut.sh

编写封禁ip脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 每十分钟执行一次封禁ip脚本 */10 * * * * cd /www/Home/ && ./blackip.sh
#!/bin/bash
logdir=/data/logs/nginx/access.log #nginx访问日志文件路径
port=443
#循环遍历日志文件取出访问量大于100的ip(忽略自己本地ip)
for drop_ip in $(cat $logdir | grep -v '127.0.0.1' | awk '{print $1}' | sort | uniq -c | sort -rn | awk '{if ($1>100) print $2}'); do
# 避免重复添加
num=$(grep ${drop_ip} /tmp/nginx_deny.log | wc -l)
if [ $num -ge 1 ]; then
continue
fi

# shellcheck disable=SC2154
iptables -I INPUT -p tcp --dport ${port} -s ${drop_ip} -j DROP
echo ">>>>> $(date '+%Y-%m-%d %H%M%S') - 发现攻击源地址 -> ${drop_ip} " >>/tmp/nginx_deny.log #记录log
done

编写封禁ip定时任务

1
2
3
4
crontab -e 

# 每十分钟执行一次封禁ip脚本
*/10 * * * * cd /www/Home/ && ./blackip.sh

误封IP后 解封 IP

1
2
3
4
5
#清空屏蔽IP
iptables -t filter -D INPUT -s 1.2.3.4 -j DROP

#一键清空所有规则
iptables -F

思路二:nginx

配置nginx的黑名单,每次填加新的黑名单之后,但还要 重载/重启Nginx。 还有一点,会让小黑子看到 403 ,所以这不一定是最优解,大概思路如下:

定时查看 access.log 日志,将这些有问题的 IP 加入黑名单

编写 sh 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
max=500 #我们设定的最大值,当访问量大于这个值得时候,封锁
confdir=/usr/local/data/nginx/conf/blockip.conf #nginx封锁配置文件路径
logdir=/usr/local/data/nginx/logs/access_huke88.log #nginx访问日志文件路径
#检测文件
test -e ${confdir} || touch ${confdir}
drop_ip=""
#循环遍历日志文件取出访问量大于500的ip
for drop_ip in $(cat $logdir | awk '{print $1}' | sort | uniq -c | sort -rn | awk '{if ($1>500) print $2}')
do
grep -q "${drop_Ip}" ${confdir} && eg=1 || eg=0;
if (( ${eg}==0 ));then
echo "deny ${drop_Ip};">>$confdir #把“deny IP;”语句写入封锁配置文件中
echo ">>>>> `date '+%Y-%m-%d %H%M%S'` - 发现攻击源地址 -> ${drop_Ip} " >> /usr/local/data/nginx/logs/nginx_deny.log #记录log
fi
done

systemctl reload nginx

定时执行

1
2
3
4
5
6
#!/bin/bash
sed -i 's/^/#&/g' /usr/local/nginx/conf/

blockip.conf #把nginx封锁配置文件中的内容注释掉

systemctl reload nginx #重置nginx服务,这样就做到了解锁IP