feat(haproxy): rate-limit POST /xmlrpc.php floods per client IP
Mirrors the existing wp-login.php brute-force protection. Generic frontend limits trigger at 300-500 req/s (sized for media-heavy pageloads), but observed xmlrpc floods run at just a few req/s for hours -- well under that ceiling while still pinning PHP-FPM workers and driving 503s fleet-wide (1,011 in one day on a single site). Adds a dedicated stick-table (xmlrpc_bruteforce, sc2) rather than reusing wp_bruteforce: sharing a counter would let wp-login and xmlrpc traffic from the same IP inflate each other's rate. Tarpits at 60 req/min/IP (double wp-login's 30, since xmlrpc is machine-to-machine and legitimately bursts -- Jetpack sync, mobile app, remote publishing). Honors the same whitelist as every other rule in the file and does not block the endpoint outright. Only safe to key on var(txn.real_ip) because of the trusted-proxy header gate shipped earlier today (2026.08.3) -- before that, per-IP tracking was trivially evaded via a spoofed X-Forwarded-For. Adds scripts/test-xmlrpc-rate-limit.py (stdlib unittest, no pytest in this repo) pinning the tracking rule, the tarpit threshold, the path_end ACL, and the whitelist exclusions. Existing trusted-proxy-gate, config-rollback, and cert-write-safety regression suites all still pass unmodified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,4 +13,19 @@ frontend stats
|
||||
# sc0 connection/rate table so the login-attempt threshold is independent of
|
||||
# the (much higher) flood thresholds.
|
||||
backend wp_bruteforce
|
||||
stick-table type ip size 100k expire 30m store http_req_rate(60s)
|
||||
|
||||
# Dedicated stick-table for POST /xmlrpc.php flood tracking.
|
||||
# Tracked via track-sc2 from the `web` frontend (hap_listener.tpl); counts
|
||||
# only xmlrpc POSTs per real client IP over a 60s window. This is a SEPARATE
|
||||
# table/counter from wp_bruteforce (sc1) rather than a shared one: both are
|
||||
# machine-to-machine WordPress endpoints an attacker could hit from the same
|
||||
# IP, and sharing a counter would let one endpoint's traffic inflate the
|
||||
# other's rate -- an IP credential-stuffing wp-login while also flooding
|
||||
# xmlrpc would trip the wp-login threshold early on xmlrpc volume alone (or
|
||||
# vice versa). track-sc1 (wp-login) and track-sc2 (xmlrpc) are each gated on
|
||||
# mutually exclusive path ACLs, so at most one of them ever fires per
|
||||
# request -- HAProxy's "one track-sc<N> per counter per request" limit is
|
||||
# never in play here since they're different counters anyway.
|
||||
backend xmlrpc_bruteforce
|
||||
stick-table type ip size 100k expire 30m store http_req_rate(60s)
|
||||
Reference in New Issue
Block a user