Predax Blog

Product updates, best practices, and deep dives into IP intelligence.

Stop WordPress Brute-Force Attacks: Login Floods, User Enumeration, and XML-RPC Amplification in 2026
Predax Team

Stop WordPress Brute-Force Attacks: Login Floods, User Enumeration, and XML-RPC Amplification in 2026

wordpressbrute-forcexml-rpcuser-enumerationwafwordpress-security

Automated brute-force against wp-login.php gets all the attention, but it's usually the least efficient part of the attack. Before a single password guess lands, most WordPress compromises start with reconnaissance: a script quietly working through ?author=1, ?author=2, and so on, harvesting valid usernames from redirects. Or a single POST to xmlrpc.php that bundles a thousand login attempts inside one HTTP request, invisible to anything watching request counts. By the time a human would call it an "attack," the automation already knows which usernames are real and has tried more passwords against them than your rate limiter ever saw.

TL;DR

  • Brute-force, user enumeration, and XML-RPC amplification are three distinct techniques — enumeration and amplification produce little or no signal in a normal failed-login log.
  • Strong passwords and even 2FA protect the login itself, but they don't stop the reconnaissance and volume-hiding tricks that precede it.
  • The fix has to happen at the IP/request layer, before WordPress's own auth, REST, or XML-RPC code ever evaluates the request.
  • Predax Security ships three purpose-built defenses for this exact problem: user/author enumeration blocking, XML-RPC hardening, and WAF auto-ban escalation — all live today.

Attack anatomy

Credential-stuffing floods against wp-login.php. Attackers don't guess passwords — they replay huge lists of already-leaked username/password pairs (from unrelated breaches) against your login form, banking on password reuse. Volume is the whole strategy: try enough pairs across enough sites and a percentage will work purely on reuse odds. For the mechanics of this at the credential-list level, see our complete guide to credential-stuffing attacks.

User/author enumeration. WordPress's default author archive behavior means requesting ?author=1 redirects to /author/admin/ (or whatever the real username is) — and it does that for every author ID whether or not you're logged in. Walk the IDs from 1 upward and you've harvested every valid username on the site without a single failed login attempt. The WP REST API's /wp/v2/users endpoint does the same job in one request, returning a full list of registered usernames to anyone who asks. Neither of these trips a login-failure counter, because neither one is a login attempt — it's reconnaissance, done quietly, so the credential-stuffing pass that follows can skip the guesswork of "which usernames even exist" and go straight to trying passwords against ones it knows are real.

XML-RPC `system.multicall` amplification. xmlrpc.php exists so WordPress mobile apps and services like Jetpack can talk to your site over a single API, and one of its features lets a caller bundle many method calls into one HTTP request via system.multicall. That's convenient for legitimate integrations — and a gift to attackers, who can pack thousands of wp.getUsersBlogs login-attempt calls into a handful of POST requests. Your server processes them all in one connection. A per-request rate limiter counting "requests per minute" sees a trickle; what's actually happening is a flood, just hidden inside the request bodies instead of the request count.

Server rack cabling under dim green lighting, representing the tangled, high-volume traffic automated brute-force and amplification tools generate against a WordPress login
Server rack cabling under dim green lighting, representing the tangled, high-volume traffic automated brute-force and amplification tools generate against a WordPress login

Why passwords and 2FA alone aren't enough

None of this is really about how strong your password is. Credential-stuffing lists are distributed across large IP pools and rotate constantly, so per-account lockouts and even 2FA only raise the cost of the *final* guess — they do nothing to the scanning and enumeration that precede it, and that reconnaissance phase is often disguised behind rotating residential and datacenter proxy infrastructure specifically so it doesn't cluster on a handful of obviously-suspicious IPs.

Enumeration attacks are the clearest example of why account-level defenses miss the point entirely: there's no login attempt to rate-limit. A visitor requesting ?author=7 a hundred times in a row isn't triggering a failed-password counter — they're not attempting a password at all. Any defense built around "count the failed logins" has literally nothing to count.

XML-RPC amplification breaks the same assumption from a different angle. A per-request threshold ("block after 20 requests/minute") looks at request *count*, and system.multicall keeps the count low while hiding the volume inside the payload. The attacker isn't sending more requests — they're sending fewer, bigger ones.

The IP-signal layer

The common thread across all three techniques is that they need to be stopped before WordPress's own login, REST, or XML-RPC handling code ever runs — every request that reaches that code has already cost your server a database connection, a plugin/theme bootstrap, and CPU cycles, whether or not it ultimately gets denied. Once a request is in-flight, you're already paying for it.

IP-layer signals — reputation, request velocity, datacenter and proxy origin — identify this traffic as automated before a single credential is checked or a single author ID is walked. A visitor coming from a known datacenter range hitting ?author=N in sequence, or a burst of XML-RPC POSTs from an IP with no prior legitimate history, looks nothing like a real visitor's traffic pattern, and that difference is visible at the network layer regardless of what the request body contains. For the broader case on why VPN/proxy/datacenter signals matter for a WordPress site generally, see blocking VPNs, proxies, and Tor on WordPress.

What Predax Security actually ships

Predax Security already blocks brute-force at the account level via configurable login-threshold blocking — lock out an IP after N failed attempts in a window (see setting login thresholds for the specifics). A July 2026 release added three more targeted defenses that go after the reconnaissance and amplification techniques login-threshold blocking was never designed to catch.

User/author enumeration blocking (ipsentry_block_user_enumeration, off by default — enable it in Settings). When switched on, it blocks ?author=<N> probes for logged-out front-end visitors — logged-in users, AJAX calls, and REST requests are deliberately left untouched so it doesn't interfere with normal site operation. It returns HTTP 401 on the WP REST /wp/v2/users and /wp/v2/users/<id> endpoints for unauthenticated requesters, with the message "Listing users is not permitted," and it strips author identity fields out of oEmbed responses too. Every blocked attempt is logged with reason user_enumeration, shown in the Threat Log as USER_ENUM.

XML-RPC hardening (ipsentry_xmlrpc_hardening) gives you three states — Off, "Block amplification," and "Disable all." The recommended middle setting is labeled in the plugin UI as "Block amplification — pingback + system.multicall (recommended)," with the description: "'Block amplification' stops brute-force amplification and pingback DDoS while keeping normal XML-RPC (e.g. the WordPress mobile app) working." Both hardened modes block system.multicall by inspecting the raw request body directly — necessary because system.multicall is registered on WordPress's XML-RPC callback list *after* the standard xmlrpc_methods filter runs, so the usual filter-based approach can't remove it. "Block amplification" additionally removes just the two pingback methods (pingback.ping and pingback.extensions.getPingbacks) — the SSRF/DDoS-reflection vector — while leaving other XML-RPC methods, like the ones the WordPress and Jetpack mobile apps rely on, working normally. "Disable all" turns XML-RPC off entirely. Both hardened modes also strip the X-Pingback HTTP header.

WAF auto-ban escalation (ipsentry_waf_escalation_enabled, off by default, with ipsentry_waf_escalation_threshold defaulting to 5 and a minimum of 2). When a visitor trips the plugin's WAF a configurable number of times inside a rolling 10-minute window, they get a durable temporary ban — 30 minutes by default, configurable — applied across *every* surface the plugin protects, not just the one endpoint that set it off. So a visitor who trips the WAF on a login-related probe gets shut out of XML-RPC, the REST API, and everywhere else the plugin watches, for the ban duration. It's logged with reason waf_escalation, shown as WAF_BAN, and it skips WP-CLI/cron requests, whitelisted IPs, and logged-in administrators so it can't lock you out of your own maintenance tooling.

All three live under Settings → WordPress Hardening in the plugin, alongside the existing login-threshold controls. Full setup details are on the WordPress integration page.

A well-organized, actively-cabled server rack, representing the layered set of purpose-built defenses Predax Security's hardening pack adds to a WordPress install
A well-organized, actively-cabled server rack, representing the layered set of purpose-built defenses Predax Security's hardening pack adds to a WordPress install

Self-check + code example

Three settings, thirty seconds:

  1. Is "Block user / author enumeration" checked?
  2. Is XML-RPC protection set to "Block amplification" or "Disable all" — not "Off"?
  3. Is WAF auto-ban escalation enabled?

If any of those are off, the corresponding attack path above is open right now regardless of how strong your admin password is.

If you're gating your own login flow (a custom login form, a headless WP setup, or an app talking to wp-login.php programmatically) and want to check an IP's risk before evaluating credentials at all, a Predax lookup on the login hook looks like this:

<?php
/**
 * Gate a login attempt on IP risk before WordPress evaluates credentials.
 * Hook this into 'authenticate' (early priority) or your own login handler.
 */
function predax_check_login_ip( $ip ) {
    $cache_key = 'predax_ip_' . md5( $ip );
    $cached    = get_transient( $cache_key );
    if ( false !== $cached ) {
        return $cached;
    }

    // POST with a JSON body — the endpoint has no GET variant; a GET 405s.
    $response = wp_remote_post(
        'https://predax.io/api/v1/check/ip',
        array(
            'headers' => array(
                'X-API-Key'    => PREDAX_API_KEY,
                'Content-Type' => 'application/json',
            ),
            'body'    => wp_json_encode( array( 'ip' => $ip ) ),
            'timeout' => 3,
        )
    );

    // Fail open: if the API call itself fails, don't block a real login attempt.
    if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
        return null;
    }

    $data = json_decode( wp_remote_retrieve_body( $response ), true );
    set_transient( $cache_key, $data, HOUR_IN_SECONDS );

    return $data;
}

add_filter( 'authenticate', function ( $user, $username, $password ) {
    if ( empty( $username ) || empty( $password ) ) {
        return $user; // let WP handle its own empty-field errors
    }

    $ip   = $_SERVER['REMOTE_ADDR'] ?? '';
    $risk = predax_check_login_ip( $ip );

    // risk_score lives under 'classification' — a top-level read returns
    // null silently and the gate never fires. (We shipped that exact bug
    // three times in our own plugins.)
    if ( $risk && ( $risk['classification']['risk_score'] ?? 0 ) >= 85 ) {
        return new WP_Error(
            'predax_blocked',
            __( 'Login temporarily unavailable from this network.', 'predax' )
        );
    }

    return $user;
}, 5, 3 );

This runs before WordPress checks the password, so a high-risk IP is turned away without your site ever hashing a credential comparison for it — the same fail-open, hourly-cache pattern used elsewhere on the blog, just wired to the login gate instead of checkout.

FAQ

What is WordPress user enumeration and why does it matter? User enumeration is the technique of discovering valid WordPress usernames without attempting a single login — most commonly by requesting ?author=1, ?author=2, and so on and following the redirect each ID produces, or by querying the WP REST API's /wp/v2/users endpoint directly. It matters because a username is half the credential pair; once an attacker has a confirmed list of real usernames, every subsequent brute-force or credential-stuffing attempt against your site becomes far more efficient, since it's no longer wasting attempts on accounts that don't exist.

Does XML-RPC amplification still matter if I don't use the WordPress mobile app or Jetpack? Yes — if XML-RPC is enabled at all (it is by default on most WordPress installs), system.multicall is reachable and abusable whether or not you personally use any XML-RPC-dependent app. If you don't use the mobile app, Jetpack, or another XML-RPC integration, "Disable all" is the simpler and more complete choice; if you do use one of those, "Block amplification" removes the multicall and pingback abuse vectors while keeping your actual integration working.

Will blocking XML-RPC break anything on my site? "Block amplification" is designed specifically not to — it only removes system.multicall and the two pingback methods, leaving other XML-RPC calls (including the ones the WordPress and Jetpack mobile apps use) functioning normally. "Disable all" is the more aggressive option and will break any tool that depends on xmlrpc.php entirely, so it's the right choice only if you've confirmed you don't use one.

Isn't a strong password and 2FA enough to stop these attacks? They stop a compromised password from being useful, but they don't touch the reconnaissance and amplification techniques described here. Enumeration never attempts a login at all, so there's no password-strength or 2FA check for it to run into, and XML-RPC amplification hides its volume inside a request body rather than the request count, bypassing the account-level defenses that 2FA and lockouts rely on. These protections work together, not as substitutes for each other.

How is this different from the general "WordPress Security in 2026" guide on the blog? That guide covers the full six-layer picture — login hardening, staying updated, attack-surface reduction, file/server hardening, IP reputation, and bot traffic — as a broad overview; see WordPress Security in 2026 for the complete picture. This post goes deep on one piece of that layer — the specific mechanics and defenses for brute-force, enumeration, and XML-RPC amplification — with the exact plugin settings and behavior rather than a general checklist.

Do I need a paid plan to get these protections? No — user/author enumeration blocking, XML-RPC hardening, and WAF auto-ban escalation are all plugin-side settings available in Predax Security regardless of your API plan tier; they run locally in WordPress and don't consume extra API quota beyond the checks the plugin already performs.