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>
31 lines
1.5 KiB
Smarty
31 lines
1.5 KiB
Smarty
# HAProxy Stats & Monitoring
|
|
frontend stats
|
|
bind 127.0.0.1:8404
|
|
stats enable
|
|
stats uri /stats
|
|
stats refresh 30s
|
|
stats show-legends
|
|
stats show-node
|
|
|
|
# Dedicated stick-table for WordPress wp-login.php brute-force tracking.
|
|
# Tracked via track-sc1 from the `web` frontend (hap_listener.tpl); counts only
|
|
# login POSTs per real client IP over a 60s window. Separate from the generic
|
|
# 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) |