通过之前的操作,我已经搭建好了wordpress个人网站,但没过多久,就收到了服务器提供商告警:CPU资源占用过高。通过日志排查(进入“my_website”文件夹后,输入命令:docker logs wordpress_nginx 2>&1 | tail -n 500
),锁定为:黑客暴力攻击式刷 xmlrpc.php 接口(日志中能看到很多个ip,不停的重复 POST,类似这样:POST //xmlrpc.php HTTP/1.1...)。
经查询,xmlrpc.php是 WordPress 提供的一个早期 API 接口,用于允许远程发布(例如通过手机App发文章),黑客利用这个接口,通过一次 POST 请求就可以尝试成百上千个用户名和密码的组合,这比攻击登录页面 wp-login.php 效率高得多。
我的网站虽然设置了每秒10个请求的限制,但攻击者用更低的频率攻击,且采用大量ip分布式攻击的方式,并掌握了管理员用户名(通过访问 https://anjir.top/?author=1获取),理论上,只要时间充足,密码一定会被攻破。又因为网站设置主要缓存静态资源,不缓存动态请求,每一次 POST 请求,Nginx 都会把它转发给后面的 WordPress (PHP-FPM) 处理。WordPress 需要连接数据库、验证信息,这个过程会消耗大量的 CPU 和内存,所以导致CPU资源占用过高告警。
要解决这个问题,其实有个很简单的解决方案:直接在 Nginx 层拒绝任何对xmlrpc.php这个文件的访问,然后只通过WordPress后台发布文章就行了。但我个人还是有用到xmlrpc接口的场景,所以不能完全封堵了。通过咨询AI,我找到了“Nginx + Fail2ban”方案,完美解决了现阶段的需求。本篇文章主要记录:使用“Nginx + Fail2ban”方案,解决黑客利用xmlrpc.php接口、wp-login.php页面暴力破解WordPress网站的问题。
简单5个步骤
- 启用并配置防火墙 UFW
- 修改 Nginx 配置,记录更详细的日志
- 配置 Fail2ban
- 设置白名单并重启 Fail2ban
- 检查封禁效果
第一步:启用并配置防火墙 UFW
-
安装: apt update && apt install ufw
-
设置默认规则: ufw default deny incoming
ufw default allow outgoing -
开放必要端口: ufw allow ssh
ufw allow http
ufw allow https -
启用 (请确保已放行SSH): ufw enable
-
检查状态: ufw status
第二步:修改 Nginx 配置,记录更详细的日志
为了让 Fail2ban 能更好地工作,我们需要让 Nginx 在日志中记录 POST
请求的具体内容。这会稍微增加日志文件的大小,但对于安全来说是值得的。
- 修改 Nginx 主配置文件
nginx.conf
我们需要修改 Nginx 的主配置文件,而不是我们站点的
app.conf
。由于我们在 Docker 中运行,这个文件在容器内部。最简单的办法是把它复制出来,修改,然后通过卷映射挂载回去。从容器中复制
nginx.conf
到宿主机cd ~/my_website
# 为主配置文件创建一个目录
mkdir -p ./nginx/main-conf
# 从正在运行的 nginx 容器中复制文件
docker cp wordpress_nginx:/etc/nginx/nginx.conf ./nginx/main-conf/nginx.conf编辑复制出来的
nginx.conf
nano ./nginx/main-conf/nginx.conf
找到
log_format main
,修改为(即末尾添加$request_body
变量,让它记录 POST 的内容):log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$request_body"'; - 修改 docker-compose.yml 挂载配置
在 my_website 目录下创建日志文件夹:
mkdir -p ./nginx/logs
修改 docker-compose.yml,为 nginx 服务添加日志卷映射,和挂载我们修改后的
nginx.conf
nano docker-compose.yml
在 nginx 服务的
volumes
部分,新增两行:volumes:
-./nginx/main-conf/nginx.conf:/etc/nginx/nginx.conf:ro# <--- 新增
-./nginx/logs:/var/log/nginx# <--- 新增
-./nginx/conf.d:/etc/nginx/conf.d - 切换网络模式为 host
(删除 ports,添加 network_mode) 我们让 Nginx 容器直接使用宿主机的网络。这样,Nginx 就能直接看到来自外部的、未经 Docker NAT 转换的原始请求,从而获取到真实的客户端公网 IP。
继续对docker-compose.yml中的 nginx 服务进行如下修改: a. 删除 ports 部分:在 host 模式下,容器直接监听宿主机的端口,不再需要端口映射。 b. 添加 network_mode: "host"。 c. 修改 wordpress 的连接方式:由于 Nginx 现在和宿主机在同一个网络命名空间,它不能再通过服务名 wordpress 来连接了。它需要通过宿主机的 localhost (127.0.0.1) 和 WordPress 容器映射出来的端口来连接。所以我们也要修改 wordpress 服务。
请将 docker-compose.yml 文件中的 nginx 和 wordpress 服务部分,修改为以下内容:
wordpress:
image:wordpress:php8.2-fpm
container_name:wordpress_fpm
restart:always
# 新增 ports 映射,让 fpm 的 9000 端口暴露给宿主机
ports:
-"127.0.0.1:9000:9000"
depends_on:
-db
volumes:
-./wordpress/html:/var/www/html
environment:
WORDPRESS_DB_HOST:db:3306
WORDPRESS_DB_USER:'wordpress_user'
WORDPRESS_DB_PASSWORD:'YOUR_STRONG_USER_PASSWORD'
WORDPRESS_DB_NAME:'wordpress'
# wordpress 仍然在 app-network 中,以便连接 db
networks:
-app-network
nginx:
image:nginx:1.27.0-alpine
container_name:wordpress_nginx
restart:always
# 关键修改:使用 host 网络模式
network_mode:"host"
# 不再需要 ports 映射
# ports:
# - "80:80"
# - "443:443"
volumes:
-./nginx/main-conf/nginx.conf:/etc/nginx/nginx.conf:ro
-./nginx/logs:/var/log/nginx
-./nginx/conf.d:/etc/nginx/conf.d
-./nginx/cache:/var/cache/nginx
-./wordpress/html:/var/www/html:ro
-./certbot/conf:/etc/letsencrypt
-./certbot/www:/var/www/certbot
# 在 host 模式下,nginx 不再属于 app-network
# networks:
# - app-network - 修改 Nginx 站点配置文件 app.conf
由于 Nginx 现在需要通过 localhost 来连接 WordPress,我们需要修改 PHP-FPM 的转发地址。
nano ./nginx/conf.d/app.conf
找到 location ~ .php$ { ... } 块,将 fastcgi_pass wordpress:9000; 修改为 fastcgi_pass 127.0.0.1:9000;
location ~ .php$ {
# ... (其他配置) ...
# 连接到宿主机的 9000 端口,该端口已映射到 WordPress 容器
fastcgi_pass 127.0.0.1:9000;
# ... (其他配置) ...
} - 重启
: docker compose down
docker compose up -d
第三步:配置 Fail2ban
-
安装: apt install fail2ban -y
-
创建 jail 配置: nano /etc/fail2ban/jail.d/wordpress-zerotolerance.conf
将以下定制好的内容粘贴进去:
[wordpress-xmlrpc]
enabled = true
port = http,https
filter = wordpress-xmlrpc
logpath = /root/my_website/nginx/logs/access.log
# 任何一次匹配都立即触发
maxretry = 1
# 封禁时间为30天
bantime = 30d
# findtime 在 maxretry=1 时意义不大,但最好保留
findtime = 1m
[wordpress-login]
enabled = true
port = http,https
filter = wordpress-login
logpath = /root/my_website/nginx/logs/access.log
# 任何一次匹配都立即触发
maxretry = 1
# 封禁时间为1天
bantime = 1d
findtime = 1m -
创建对应的 filter
(过滤器) 文件为
xmlrpc.php
创建 filternano /etc/fail2ban/filter.d/wordpress-xmlrpc.conf
粘贴以下内容:
[Definition]
failregex = ^<HOST> - .* "POST /+xmlrpc.php HTTP/.*
ignoreregex =以及为
wp-login.php
创建 filternano /etc/fail2ban/filter.d/wordpress-login.conf
粘贴以下内容:
[Definition]
failregex = ^<HOST> - .* "POST /+wp-login.php.*
ignoreregex =
第四步:设置白名单并重启 Fail2ban
这一步至关重要,防止你被自己锁在外面。
-
编辑 jail.local 添加你自己的 IP nano /etc/fail2ban/jail.local
找到或添加
ignoreip
这一行,并加上你自己的本机 IP段(百度“ip”,家用宽带动态ip前3段一般不变,最后一段全包含即可,比如百度查询到你此时的ip是:117.100.100.100,那么加白的ip段可以填:117.100.100.0/24)。[DEFAULT]
ignoreip = 127.0.0.1/8 ::1117.100.100.0/24 -
重启 Fail2ban 使所有配置生效 systemctl restart fail2ban
-
检查 Fail2ban 状态 fail2ban-client status
你应该能看到
Jail list:
下面有wordpress-login
和wordpress-xmlrpc
这两个活动的 jail。
第五步:检查封禁效果
wp-login.php封禁验证:
a. 进行测试:
-
用你的手机网络(不要用常用 Wi-Fi)访问 https://anjir.top/wp-login.php。
-
只输入一次错误的密码。
-
然后尝试刷新页面或再次访问。你应该会发现网站已经无法访问了。
-
回到服务器,执行 fail2ban-client status wordpress-login,你会看到你手机的 IP 已经被列入了封禁列表。
b. 练习解封:
既然测试成功了,就练习一下解封自己的手机IP吧。
-
1 -
2
# 假设你手机IP是 222.222.222.222
fail2ban-client unban 222.222.222.222
xmlrpc.php封禁验证:
打开2个SSH 终端连接服务器窗口,一个用来操作登录,另一个用来看日志。
-
监控日志:
tail -f /root/my_website/nginx/logs/access.log
等待新的攻击日志出现。当下一条类似 5.101.157.127 ... "POST /xmlrpc.php ..." 的日志出现时,Fail2ban 就会立刻匹配到它。
检查 Fail2ban 状态:
稍等片刻后,再次执行:
fail2ban-client status wordpress-xmlrpc
这一次,你应该会看到 Currently failed 和 Total failed 的计数增加了,并且 Banned IP list 中出现了新的被封禁的 IP。
未经允许不得转载:无花果 » 防止黑客利用xmlrpc.php接口分布式暴力破解WordPress网站