288,493 个请求 – 我如何通过奇怪的缓存比率发现了一个 XML-RPC 暴力破解
288,493 Requests – How I Spotted an XML-RPC Brute Force from a Weird Cache Ratio

原始链接: https://marcindudek.dev/blog/xmlrpc-brute-force-cache-rate/

最近一次WordPress网站攻击凸显了一个关键的监控点:**缓存命中率**。 尽管网站仍然在线,但由于来自新加坡DigitalOcean单个IP地址的288,493个POST请求涌向`/xmlrpc.php`,缓存率骤降至0.8%。 此次攻击利用`system.multicall`函数,试图在每个请求中尝试数百组凭据组合,绕过典型的速率限制。 根本原因是一个未修补的漏洞,利用了较旧的`xmlrpc.php`接口。 修复方案采用了双层方法:Cloudflare WAF规则在边缘阻止`/xmlrpc.php`,并在WordPress本身内部禁用`xmlrpc`(使用WP Multitool的Frontend Optimizer,现在在1.1.19版本中默认启用)。 **关键要点:** 定期检查Cloudflare Top Paths – `/xmlrpc.php`请求的突然激增表明存在攻击。 不要仅仅依赖于正常运行时间监控; 缓存率下降是至关重要的早期预警信号。 除非您特别需要它(例如Jetpack移动端),否则建议禁用`xmlrpc.php`以提高安全性。 主动WAF规则和纵深防御策略对于保护WordPress网站至关重要。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 288,493 请求 – 我如何通过奇怪的缓存比率发现 XML-RPC 暴力破解 (marcindudek.dev) 4 分,taubek 发表于 2 小时前 | 隐藏 | 过去 | 收藏 | 2 条评论 帮助 dwedge 发表于 22 分钟前 | 下一个 [–] 我真的很快就到了无法阅读 LLM 文章的地步。我尝试了,因为它表面上看起来很有趣,但在第三或第四次重复后,我厌倦了阅读一篇 a) 比需要长 50% 的文章,并且 b) 作者没有费心去写作的文章。 VladVladikoff 发表于 8 分钟前 | 上一个 [–] 如果 Cloudflare 甚至无法过滤掉最基本的 WordPress 暴力破解攻击,那它的作用是什么? 另外,文章是垃圾 AI LLM 生成的内容,让人难以阅读。 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

TL;DR

  • Cloudflare cache hit ratio on a WordPress site dropped to 0.8% — the real alarm, not CPU or uptime.
  • Root cause: a single Singapore DigitalOcean IP flooding /xmlrpc.php with 288,493 POSTs in 24 hours, using system.multicall to brute-force hundreds of credentials per request.
  • Fix: Cloudflare WAF rule blocking /xmlrpc.php at the edge, plus WP Multitool's Frontend Optimizer disabling xmlrpc inside WordPress — defense in depth, both layers on by default in 1.1.19.
  • Action for you: check your Cloudflare Top Paths weekly. If xmlrpc.php shows up in the top 3, you're already being hit.

Today I was glancing at Cloudflare analytics for one of my sites and something looked off. Cache hit ratio: 0.8%. That's not a typo. Zero point eight percent.

For a mostly-static WordPress site that should be sitting at 70–90%, 0.8% means something is very wrong. Either the cache rules are broken, or something is flooding the site with uncacheable traffic. Turned out to be the second one.

I pulled up the traffic breakdown and the answer was sitting right there. One IP from Singapore, 288,493 requests in 24 hours, all POSTs to /xmlrpc.php, all returning 200. That's about 12,000 requests per hour from a single DigitalOcean droplet. The site was still up because Cloudflare was absorbing most of it, but my origin was burning CPU on every single one of those requests.

Why 0.8% Cache Rate Was the Real Signal

Here's the thing about WordPress attacks on xmlrpc.php — they're almost invisible if you only watch uptime. The site loads fine for real visitors. The CPU graph might look a bit elevated. But the attack itself doesn't trigger any obvious alarm.

Cache rate is a great canary because xmlrpc.php is POST-only and marked dynamic. Every attack request counts against your cache rate denominator. When you see 288k uncacheable dynamic requests vs a few thousand normal cached ones, the ratio collapses.

So if your cache rate suddenly drops on an otherwise quiet WordPress site, don't immediately blame your plugins. Check what's being requested.

What system.multicall Actually Does

The vector is old but still works because so many sites leave xmlrpc enabled by default. Attackers POST XML payloads like this:

<?xml version='1.0'?>
<methodCall>
  <methodName>system.multicall</methodName>
  <params>
    <param><value><array><data>
      <value><struct>
        <member><name>methodName</name><value>wp.getUsersBlogs</value></member>
        <member><name>params</name><value><array><data>
          <value><string>admin</string></value>
          <value><string>password1</string></value>
        </data></array></value></member>
      </struct></value>
      ... [hundreds more credential pairs]
    </data></array></value></param>
  </params>
</methodCall>

One HTTP request, hundreds of login attempts. That's the amplification. A regular wp-login.php brute force triggers rate limiting and WAF rules after a few attempts. system.multicall lets you test 500 credentials in a single POST that looks like normal API traffic. That's why it's still the preferred vector in 2026 — efficient, quiet, and slips past naive rate limits that count requests instead of auth attempts.

How to Check If You're Being Hit

If you're on Cloudflare, the fastest way is the GraphQL analytics API. Query the top paths for the last 24 hours, grouped by path and cache status:

query($zone:String!, $since:Time!, $until:Time!) {
  viewer {
    zones(filter:{zoneTag:$zone}) {
      httpRequestsAdaptiveGroups(
        limit:20,
        filter:{datetime_geq:$since, datetime_leq:$until},
        orderBy:[count_DESC]
      ) {
        count
        dimensions { clientIP clientCountryName clientRequestPath cacheStatus }
      }
    }
  }
}

If /xmlrpc.php shows up in the top 3 paths with cacheStatus "dynamic" and a request count in the tens of thousands, you're being hit. Also check wp-login.php while you're there — where there's xmlrpc abuse there's usually a coordinated login flood from other IPs.

If you don't use the API, the Cloudflare dashboard has the same data under Analytics → Traffic. The Top Paths widget will give it away.

Fix #1 — Block It at the Edge

The fastest mitigation is a Cloudflare WAF custom rule. One line expression, block action:

(http.request.uri.path eq "/xmlrpc.php")

This stops the requests at Cloudflare's edge before they reach your origin. No CPU cost, no PHP execution, no bandwidth. After I deployed this rule the 288k/day flood stopped hitting my server entirely and the cache rate started recovering within the hour.

If you have dozens of zones to protect, the Cloudflare API makes this a one-liner per zone. I ran it on all my own zones in about 10 seconds.

Fix #2 — Disable It at the WordPress Level Too

Edge blocks are great but I'm a defense-in-depth person. If someone misconfigures the WAF or bypasses Cloudflare, I want WordPress itself to refuse xmlrpc requests.

You can do it with a few lines in functions.php:

add_filter('xmlrpc_enabled', '__return_false');
add_filter('xmlrpc_methods', function($methods) { return []; });

Or if you'd rather not touch code, any decent WordPress optimizer plugin has this option. My own plugin WP Multitool has it in the Frontend Optimizer module and it's on by default in the latest version — which is why I updated all my sites to 1.1.19 after spotting this attack. Install, activate, done — xmlrpc is disabled both ways.

The key insight: don't rely on a single layer. Edge WAF rules can be misconfigured, origin can be exposed directly, and plugins can be deactivated. Stack them so no single failure leaves you open.

Do You Actually Need xmlrpc in 2026?

Probably not. xmlrpc.php was the WordPress API before REST existed. These days:

  • The WordPress REST API handles everything xmlrpc used to do, better and with proper auth
  • Remote publishing apps have mostly moved to REST
  • Even WooCommerce and major page builders don't touch xmlrpc

The one remaining legit use case is the Jetpack mobile app, which still uses xmlrpc under the hood for some features. If you don't use Jetpack mobile, you can kill xmlrpc without losing anything. If you do, there are better ways — restrict xmlrpc to Jetpack's IP ranges via WAF rule instead of blocking outright.

In my case none of my sites need it, so I blocked it everywhere.

What I'd Do Going Forward

A few habits I'm adding after this:

Watch cache rate, not just uptime. A 5% drop in hit ratio on a static-ish site deserves a look. A 50% drop means something is happening right now.

Check top paths weekly. Five seconds in the Cloudflare dashboard, tells you immediately if an attack is warming up. Most brute force attempts start small and ramp up over days.

Block xmlrpc.php preemptively on new sites. It's one WAF rule. There's no reason to wait until you see 288k requests before adding it.

And if you're running WordPress anywhere without a CDN or WAF in front of it, fix that first. Cloudflare's free tier would have absorbed this entire attack without breaking a sweat.

The cache rate on my site is back to normal now. But I'm genuinely curious how many WordPress owners are getting hit right now and have no idea because they only check if the homepage loads.

联系我们 contact @ memedata.com