#!/bin/bash # 防止上一次任务还没结束时重复运行 exec 9>/tmp/nginx-auto-ban.lock flock -n 9 || exit 0 # ================================================== # 封禁条件 # ================================================== # 单个IP两分钟达到50次,直接封禁 IP_THRESHOLD=50 # 同一个/24网段两分钟合计达到120次 SUBNET_THRESHOLD=120 # 同一个网段至少出现5个不同IP SUBNET_MIN_IPS=5 # 管理员固定IP,没有就保持为空 ADMIN_IPS=() # 公共封禁文件 BAN_FILE="/www/server/nginx/conf/blockips.conf" # Nginx程序 NGINX="/www/server/nginx/sbin/nginx" # ================================================== # 时间范围 # ================================================== # 当前分钟 NOW=$(LC_ALL=C date '+%d/%b/%Y:%H:%M') # 上一分钟 PREV=$(LC_ALL=C date -d '1 minute ago' '+%d/%b/%Y:%H:%M') # ================================================== # 临时文件 # ================================================== OLD_FILE="/tmp/nginx-blockips-old.$$" NEW_FILE="/tmp/nginx-blockips-new.$$" REQUEST_FILE="/tmp/nginx-request-ips.$$" INDIVIDUAL_FILE="/tmp/nginx-individual-ips.$$" SUBNET_FILE="/tmp/nginx-subnet-ips.$$" IP_FILE="/tmp/nginx-final-ips.$$" trap ' rm -f "$OLD_FILE" rm -f "$NEW_FILE" rm -f "$REQUEST_FILE" rm -f "$INDIVIDUAL_FILE" rm -f "$SUBNET_FILE" rm -f "$IP_FILE" ' EXIT # ================================================== # 准备封禁文件 # ================================================== touch "$BAN_FILE" # 保存修改前的文件 cp -a "$BAN_FILE" "$OLD_FILE" cp -a "$BAN_FILE" "$NEW_FILE" # ================================================== # 读取所有网站最近两分钟的访问日志 # ================================================== for FILE in /www/wwwlogs/*.log do [ -f "$FILE" ] || continue # 排除错误日志和系统日志 case "$FILE" in *.error.log|*/nginx_error.log|*/tcp-access.log|*/tcp-error.log) continue ;; esac # 每个网站只读取最后5000行 tail -n 5000 "$FILE" 2>/dev/null | awk -v now="$NOW" -v prev="$PREV" ' { # 只统计当前分钟和上一分钟 if (index($0, "[" now) == 0 && index($0, "[" prev) == 0) { next } # 第7列通常是请求地址 uri = tolower($7) # 删除问号后面的查询参数 sub(/\?.*/, "", uri) # 静态资源不参与统计 if (uri ~ /\.(js|css|map|jpg|jpeg|png|gif|webp|bmp|svg|ico|swf|woff|woff2|ttf|eot|otf|mp3|wav|ogg|mp4|webm|avi|mov)$/) { next } # 只统计IPv4 if ($1 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) { print $1 } } ' done > "$REQUEST_FILE" # ================================================== # 第一层:统计单个IP # ================================================== sort "$REQUEST_FILE" | uniq -c | awk -v threshold="$IP_THRESHOLD" ' $1 >= threshold { print $2 } ' > "$INDIVIDUAL_FILE" # ================================================== # 第二层:统计同一个/24网段 # # 例如: # 112.86.225.20 # 112.86.225.33 # 112.86.225.59 # # 都会被归入112.86.225这个网段 # ================================================== awk -F. \ -v threshold="$SUBNET_THRESHOLD" \ -v min_ips="$SUBNET_MIN_IPS" ' { ip = $0 subnet = $1 "." $2 "." $3 # 统计网段总请求数 total[subnet]++ # 记录这个网段下出现过的不同IP key = subnet SUBSEP ip seen[key] = 1 } END { # 计算每个网段出现了多少个不同IP for (key in seen) { split(key, parts, SUBSEP) subnet = parts[1] unique_count[subnet]++ } # 网段达到条件后,输出这个网段当前出现的所有IP for (key in seen) { split(key, parts, SUBSEP) subnet = parts[1] ip = parts[2] if (total[subnet] >= threshold && unique_count[subnet] >= min_ips) { print ip } } } ' "$REQUEST_FILE" | sort -u > "$SUBNET_FILE" # 合并两种检测结果并去重 cat "$INDIVIDUAL_FILE" "$SUBNET_FILE" | sort -u > "$IP_FILE" # ================================================== # 将达到条件的IP加入公共封禁文件 # ================================================== CHANGED=0 while read -r IP do [ -n "$IP" ] || continue # 不封禁本机地址 case "$IP" in 127.*) continue ;; esac # 检查IP是否属于管理员白名单 IS_ADMIN_IP=0 for ADMIN_IP in "${ADMIN_IPS[@]}" do if [ "$IP" = "$ADMIN_IP" ] then IS_ADMIN_IP=1 break fi done # 管理员IP不加入封禁文件 if [ "$IS_ADMIN_IP" -eq 1 ] then continue fi RULE="deny $IP;" # 判断封禁原因 REASON="" if grep -qFx "$IP" "$INDIVIDUAL_FILE" then REASON="单IP高频" fi if grep -qFx "$IP" "$SUBNET_FILE" then if [ -n "$REASON" ] then REASON="$REASON、同网段高频" else REASON="同网段高频" fi fi # 已经封禁的不重复添加 if ! grep -qFx "$RULE" "$NEW_FILE" then echo "$RULE" >> "$NEW_FILE" echo "$(date '+%F %T') 准备封禁IP:$IP,原因:$REASON" CHANGED=1 fi done < "$IP_FILE" # 没有发现新的IP if [ "$CHANGED" -eq 0 ] then echo "$(date '+%F %T') 检查完成,没有发现新的高频IP" exit 0 fi # ================================================== # 检测并应用新的Nginx封禁文件 # ================================================== cp -a "$NEW_FILE" "$BAN_FILE" if "$NGINX" -t then "$NGINX" -s reload echo "$(date '+%F %T') 新IP已经封禁,Nginx重载成功" else cp -a "$OLD_FILE" "$BAN_FILE" echo "$(date '+%F %T') Nginx检测失败,已经恢复旧文件" exit 1 fi