本文约 3,072 字,预计阅读 8 分钟。文中命令与配置均经过实测,可直接参考执行。
引言
凌晨收到主机告警,说 CPU 使用率长时间 100%,网站打开慢到几乎打不开,后台也进不去。登进服务器用 top 一看,清一色是 PHP-FPM 进程在跑,打开访问日志(Nginx 的 access.log),满屏都是同一个文件:/wp-admin/admin-ajax.php,来自一堆陌生 IP,每秒几百次。这就是典型的 admin-ajax.php 被恶意刷。admin-ajax.php 是 WordPress 处理 AJAX 请求的动态入口,前台点赞、加载更多、购物车、搜索联想全靠它,但它每次都要启动 WordPress 核心、连数据库,开销不小。正常访问量下没问题,可一旦被自动化脚本高频刷取,每个请求都要实打实消耗 CPU 和数据库资源,服务器瞬间就被压垮。这类问题不及时处理,轻则网站卡死,重则被主机停号。
故障原因分析
- 被自动化脚本刷取。攻击者或采集程序直接用固定 IP 或代理池高频请求
admin-ajax.php,正常访客根本产生不了这么高的频率。
admin-ajax接口未做来源校验。默认情况下这个入口对所有请求开放,恶意请求无需登录也能触发,插件暴露的动作没做 nonce 和权限校验就更容易被利用。
- 未登录用户触发的动作太重。有的插件把「加载更多文章」「获取评论」这类动作也交给未登录访客触发,每次都要查库,频率一高就拖垮 PHP。
- 缺少缓存和限流。
admin-ajax.php的响应通常不缓存,服务器层面也没做 IP 限速,请求来多少处理多少。
- 被 DDoS 或代理池轮换 IP。攻击者换 IP 很快,简单拉黑单个 IP 治标不治本,需要识别特征而非只认 IP。
分步解决方案
第一步:确认问题来源
先看日志统计哪个 IP、哪个动作请求最多。Nginx 环境:
# 统计访问 admin-ajax.php 最多的前 20 个 IP
awk '/admin-ajax.php/ {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
再看请求里带什么参数(action= 后面的值),能判断是哪个插件接口被刷:
# 统计被请求最多的 action 参数
grep 'admin-ajax.php' /var/log/nginx/access.log | grep -o 'action=[a-zA-Z0-9_-]*' | sort | uniq -c | sort -rn | head
WordPress 装 Wordfence、Limit Login Attempts、WP Cerber 这类安全插件,也能看到实时攻击和封禁记录。
第二步:用 CDN 或 WAF 拦截
最省力的办法是在 CDN/WAF 层拦截,不占用你的服务器资源。在 CDN 后台给 /wp-admin/admin-ajax.php 配置频率限制规则,比如同一 IP 每分钟超过 60 次就返回 429 或直接拦截;对来自国外的异常流量也可按地域限流。Cloudflare 的 Rate Limiting、又拍云/腾讯云的 WAF 都有类似功能。
第三步:在 Nginx 层做 IP 限速与拉黑
没有 WAF,可在源站 Nginx 加限速。先定义限速区,再对 admin-ajax 施加限制:
# nginx.conf 的 http 段定义限速区
limit_req_zone $binary_remote_addr zone=wpajax:10m rate=30r/m;
# 站点 server 段内限制 admin-ajax
location = /wp-admin/admin-ajax.php {
limit_req zone=wpajax burst=10 nodelay;
limit_req_status 429;
try_files $uri =404;
}
含义是每个 IP 每分钟最多 30 次请求,允许突发 10 次,超了返回 429。同时把日志里抓到的恶意 IP 段直接封禁:
# 封禁指定 IP 段
deny 1.2.3.0/24;
封禁行数多时,用单独的 deny.conf 文件统一管理并 include 进来。
第四步:只允许带合法来源的请求
正常的 admin-ajax 请求会带 Referer 头,纯脚本刷通常不带或伪造。可以拒绝空 Referer 的请求(注意会误伤部分正常场景,需评估):
location = /wp-admin/admin-ajax.php {
if ($http_referer = "" ) { return 403; }
limit_req zone=wpajax burst=10 nodelay;
try_files $uri =404;
}
更稳妥的是靠 WordPress 层校验 nonce,但这需要插件配合,无法阻止请求本身到达 PHP,所以服务器层限流仍是主力。
第五步:加固 WordPress 与插件
把 WordPress 核心、主题、插件全部更新到最新版,很多被刷的接口是旧版本漏洞。检查是否有插件把重动作暴露给未登录用户:在插件代码里搜索 wp_ajax_nopriv_,这个钩子代表「未登录用户也能调用」,确认每个 nopriv 动作都有必要的校验或限流。用安全插件(Wordfence、iThemes Security)开启防火墙规则,自动拦截高频请求。
第六步:接管前端 AJAX 请求
很多高频率动作(点赞、浏览量统计)其实可以不走 admin-ajax。把前端请求改由 CDN 边缘函数处理,或用一个极轻量的独立入口,甚至在服务端用缓存合并请求,减少每次打 WordPress 核心的代价。都能大幅降低 CPU 压力。
第七步:确认恢复并持续观察
处理完再看 top 的负载、PHP-FPM 进程数是否回落,网站能否正常打开。观察一两天日志,确认没有卷土重来,必要时迭代限流阈值。
常见踩坑点
- 只封单个 IP。攻击者用代理池轮换 IP,封一个换一个,必须配合频率限制和特征拦截。
- 直接禁用 admin-ajax。会导致点赞、加载更多、购物车等前台功能全部失效,只能限流不能一刀切禁用。
- Referer 校验过严误伤。部分前台场景或隐私浏览器不带 Referer,配置拦截前先小范围观察,别把正常用户也拦了。
- 限速阈值设太狠。正常用户快速连续操作可能触发 429,阈值要结合真实访问量设定,宁可放宽一点。
- 忽略日志留存。没有日志就无法定位攻击源,建议至少保留 7 天访问日志,方便追溯。
总结
服务器 CPU 跑满、admin-ajax.php 被恶意请求,本质上是这个动态接口被人当成免费的计算入口高频刷取。处理要分两层:先在服务器或 WAF 层做限流和拦截,这是最直接的止损手段,用 Nginx 的 limit_req 或 CDN 频率限制,把每个 IP 的请求数压下去;再从 WordPress 层加固,更新组件、审计 wp_ajax_nopriv_ 暴露的动作、装安全插件,并把高频动作改造为更轻的入口。封 IP 只能应急,限流加特征识别才是长久之计。把它当成日常安全巡检的一部分,你的服务器就不会再因为一个接口被刷而整夜告警。
