【实战分享】基于 Nginx + Fail2ban 的 Discuz! X 高级防刷方案(精准拦截恶意收藏、搜索爆破、WP 扫描器)
大家好!近期我的 Discuz! X 站点(www.dianbo.org)频繁遭遇自动化脚本攻击,包括:
[*]恶意刷 /search.php?source=hotsearch 导致 CPU 飙升
[*]自动化脚本批量“收藏”帖子或板块(/home.php?ac=favorite),无 Referer 或自引用
[*]大量 WordPress 路径扫描(如 wp-login.php, xmlrpc.php)
[*]高频 PHP 请求试探漏洞
经过一段时间优化,我构建了一套 Nginx 层精准拦截 + Fail2ban 动态封禁 的组合防御体系,效果显著:日均拦截异常请求 数万次,服务器负载明显下降。
现将完整配置开源分享,欢迎交流优化建议!
一、Nginx 核心防护配置(基于宝塔环境)
环境说明:宝塔面板 + Nginx 1.26 + PHP 8.2(Unix Socket: /tmp/php-cgi-82.sock)
1. 通用限流与判断逻辑(置于 nginx.conf 的 http{} 块中)
# 优化大黑名单性能(支持 15w+ IP)
map_hash_max_size 262144;
map_hash_bucket_size 256;
# Fail2ban 持久化黑名单(可选)
map $remote_addr $blocked_ip {
default 0;
include /www/fail2ban-persist/bad-ips.map;
}
# 判断是否为收藏操作
map $args $is_favorite {
default 0;
~(^|&)ac=favorite(&|$) 1;
}
# 判断 Referer 是否为空
map $http_referer $is_empty_referer {
default 0;
"" 1;
}
# 判断是否为自引用(Referer 与当前 URI 相同)
map "$http_referer|$request_uri" $is_self_referer {
default 0;
"~*^https?://[^/]+(/home\.php\?.+)\|\1$" 1;
}
# 限流 Zone 定义
limit_req_zone $binary_remote_addr zone=search:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=test:10m rate=1r/s;
limit_req_zone $server_name zone=search_total:10m rate=30r/s;
limit_req_zone $binary_remote_addr zone=general:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=2r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=attachment:10m rate=3r/s;
limit_req_zone $binary_remote_addr zone=malicious:10m rate=3r/s;# 高危请求
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
# 日志辅助配置(宝塔默认路径)
include /www/server/nginx/conf/log_filter.conf;
include /www/server/nginx/conf/404_map.conf;
2. 精准站点规则(置于站点配置文件,如 www.dianbo.org.conf)# 🔍 搜索接口防刷(独立限流 + 屏蔽恶意参数)
location = /search.php {
if ($args ~* "(^|&)source=hotsearch(&|$)") {
access_log off;
return 444;# 静默丢弃
}
limit_req zone=search burst=5 nodelay;
limit_req zone=search_total burst=10;
fastcgi_pass unix:/tmp/php-cgi-82.sock;
fastcgi_index index.php;
include fastcgi.conf;
fastcgi_param PHP_ADMIN_VALUE "open_basedir=$document_root/:/tmp/:/proc/";
}
# ❤️ 收藏操作防护(防自动化脚本)
location = /home.php {
set $should_block "";
if ($is_favorite = "1") { set $should_block "F"; }
if ($is_empty_referer = "1") { set $should_block "${should_block}E"; }
if ($is_self_referer = "1") { set $should_block "${should_block}S"; }
# 拦截:收藏 + 无 Referer(FE) 或 收藏 + 自引用(FS)
if ($should_block = "FE") { access_log off; return 444; }
if ($should_block = "FS") { access_log off; return 444; }
include fastcgi.conf;
fastcgi_pass unix:/tmp/php-cgi-82.sock;
fastcgi_index index.php;
fastcgi_param PHP_ADMIN_VALUE "open_basedir=$document_root/:/tmp/:/proc/";
}
# 🖼️ 用户头像免限流(确保体验)
location = /uc_server/avatar.php {
fastcgi_pass unix:/tmp/php-cgi-82.sock;
include fastcgi.conf;
fastcgi_param PHP_ADMIN_VALUE "open_basedir=$document_root/:/tmp/:/proc/";
# 注意:此处未加 limit_req
}
# 🚫 静默拦截 WordPress 扫描器(Discuz 不需要这些路径)
location ~* ^/(?:xmlrpc\.php|wp-login\.php|wp-admin|wp-config\.php|wp-content|wp-includes|wp-json|\.env|readme\.html|license\.txt)(?:$|/) {
access_log off;
log_not_found off;
return 444;
}
💡 说明:return 444 是 Nginx 特有指令,直接关闭连接且不返回任何响应,有效节省资源。
经验:能用 Nginx 拦截的话,尽量不用 Fail2ban,以节省 PHP 开销。Fail2ban 仅做兜底。
二、Fail2ban 动态封禁策略1. jail.local 配置
ignoreip = 127.0.0.1/8
# 如需添加可信运维 IP,请在上一行后面追加,例如:
# ignoreip = 127.0.0.1/8 203.0.113.100
bantime = 1d
findtime = 300
maxretry = 2
banaction = iptables-ipset
logpath_discuz = /www/wwwlogs/www.dianbo.org.log
enabled = true
port = ssh,26444
logpath = /var/log/auth.log
maxretry = 2
enabled = true
filter = nginxwpscan-ipset
logpath = %(logpath_discuz)s
maxretry = 3
findtime = 1h
enabled = true
filter = nginxmalicious-ipset
logpath = %(logpath_discuz)s
maxretry = 1
findtime = 1d
enabled = true
filter = search-ipset
logpath = %(logpath_discuz)s
maxretry = 1
findtime = 600
2. 示例 Filter:search-ipset.conf 用于检测异常中断或高频失败请求:
# /etc/fail2ban/filter.d/search-ipset.conf
datepattern = ^\[%%d/%%b/%%Y:%%H:%%M:%%S
failregex = ^<HOST> - - \[.*\] "(GET|POST) /+(search|home|member|misc|portal)\.php\?[^"]*formhash={8}[^"]*" (?:\d{3} )?503
^<HOST> - - \[.*\] "POST /member\.php\?mod=logging&action=login[^"]*" 499 \d+
^<HOST> - - \[.*\] "GET /misc\.php\?mod=seccode&[^"]*" 503 \d+
ignoreregex =
以上仅为示例,请从自己的网站日志提取专属攻击特征,构造自己的专属规则。
三、效果与建议
[*]✅ CPU 负载下降 40%+:静默丢弃无效请求,减少 PHP-FPM 压力
[*]✅ 自动化脚本基本失效:收藏、搜索刷量归零
[*]✅ 日志干净:WP 扫描器不再污染 access.log
[*]⚠️ 建议:
[*]定期清理 /www/fail2ban-persist/bad-ips.map(可配合 cron)
[*]对 /member.php 登录接口单独限流(如 zone=login)
[*]结合 Cloudflare 等 CDN 可进一步前置过滤
四、结语
这套方案已在生产环境稳定运行接近一个月,兼顾安全性与用户体验。所有配置均基于宝塔默认路径,可直接复用。
欢迎各位站长测试、反馈,也期待看到大家的优化思路!
附:我的站点 www.dianbo.org 已启用上述防护,欢迎“友好探测” 😄提醒:防范不能一劳永逸,需要时刻关注网站日志,以发现新的威胁,及时更新防护规则。
另:为了监控防范效果,制作了一个网站安全状态仪表盘,放在页面右下角:
点开效果如图:在攻击最高的一些日子,每天新增3万多个IP,一个多星期时间持久化黑名单已经封顶到50万,于是索性关了热搜功能(清空所有热搜词),然后在Nginx层对热搜直接进行444拦截,图上的数据一下子清静了。如果你想了解持久化黑名单的设置,欢迎回帖交流。
页:
[1]