应优先在 wp_options 表中设置 enable_xmlrpc=0 并清理缓存,再通过 nginx/apache 拦截 xmlrpc.php 请求,同时清除 wp_usermeta 中的用户级 xml-rpc 开关字段,避免局部绕过。
直接禁用 wp_xmlrpc_server 的数据库开关
wordpress 的 xml-rpc 功能是否启用,不只看插件或配置文件,核心控制在 wp_options 表的 enable_xmlrpc 选项上。这个值默认不存在(php 层 fallback 为 1),一旦手动插入,就会覆盖逻辑。
常见错误是只停用插件或改 functions.php,但攻击者仍能发请求触发 xmlrpc.php 入口——因为底层服务没真正关死。
- 执行 SQL:
INSERT INTO wp_options (option_name, option_value, autoload) VALUES ('enable_xmlrpc', '0', 'yes') ON DUPLICATE KEY UPDATE option_value = '0'; - 如果表前缀不是
wp_,请替换为实际前缀(如myblog_options) - 操作后务必清空对象缓存(如 Redis、Memcached),否则旧值可能被缓存住
- 验证是否生效:访问
https://yoursite.com/xmlrpc.php应返回 HTTP 500 或空白页,且curl -X POST -d '<methodcall><methodname>system.listMethods</methodname></methodcall>' https://yoursite.com/xmlrpc.php不再返回方法列表
删掉 wp_usermeta 里的 XML-RPC 相关残留字段
某些安全插件或手动加固时会往 wp_usermeta 插入类似 disable_xmlrpc 或 xmlrpc_disabled 的用户级开关,这些字段优先级高于全局选项,容易被忽略。
它们不会报错,但会让部分用户(尤其是管理员)的 XML-RPC 请求绕过全局关闭逻辑,形成“局部开启”漏洞。
- 检查是否存在干扰字段:
SELECT meta_key FROM wp_usermeta WHERE meta_key LIKE '%xmlrpc%';
- 批量清理(谨慎!仅当确认无业务依赖时):
DELETE FROM wp_usermeta WHERE meta_key IN ('disable_xmlrpc', 'xmlrpc_disabled', 'xmlrpc_enabled'); - 注意:有些主题或插件用这些字段做细粒度控制,删前建议 grep 代码库确认调用点
别碰 wp_xmlrpc_server 类文件本身
有人想“彻底删除”XML-RPC,就直接删 wp-includes/class-wp-xmlrpc-server.php 或注释掉 xmlrpc.php 入口。这会导致 WordPress 升级失败、核心校验报错,甚至引发白屏。
更糟的是,部分 REST API 路由(如 /wp-json/wp/v2/users/me)内部会间接调用 XML-RPC 工具函数,删文件可能让后台用户编辑异常。
- 正确做法是保留文件,只切断入口和开关
- 若必须拦截请求,用 Web 服务器层(如 Nginx 的
location ~ ^/xmlrpc\.php$ { return 403; })比动 PHP 文件更安全 - Apache 用户可加
RewriteRule ^/xmlrpc\.php$ - [F,L]到.htaccess,但需确保该规则在 WordPress 主 rewrite 块之前
为什么 wp_options 改了还被爆破?
改完 enable_xmlrpc 还看到大量 POST /xmlrpc.php 日志,不是没生效,而是 WordPress 默认仍会加载并解析请求头——只是最后才拒绝。攻击扫描器不在乎响应内容,只要 HTTP 状态码不是 404 就继续扫。
真正的防御水位要拉到 Web 服务器层,否则数据库和 PHP 层仍在消耗资源。
- 推荐组合:数据库关开关 + Nginx/Apache 拦截 + fail2ban 监控
xmlrpc.php访问频率 - fail2ban 规则示例(匹配 5 分钟内超 3 次 POST):
^.*POST \/xmlrpc\.php.*$
- 注意:Cloudflare 等 CDN 后端的真实 IP 可能被掩盖,
fail2ban需配合nginx的real_ip_header正确识别
最易被忽略的是缓存层和 CDN 缓存了旧的 200 响应,或者多节点部署时只改了一个库。改完一定要查日志里是不是真没新请求进来,而不是只信数据库里那个 0。











