Security
Your WordPress site is publishing your admin login
A default WordPress install hands out the administrator username to anyone who asks, in two different ways. I found it on my own site this week. Here is the check, and the fix.
- Published
- 10 September 2026
- Reading time
- 6 min
Every credential attack needs two halves. Attackers have enormous lists of leaked passwords, and they have automation to try them. What they do not automatically have is your username.
WordPress gives it to them. By default, in two separate ways, to anyone who asks, without logging in.
I found both on my own site this week, while running a hardening pass on a build I had been treating as finished. This is not a story about a badly maintained site. It is a story about defaults.
The first door: the author parameter
Request your own home page with a query parameter appended:
curl -sI "https://yoursite.com/?author=1"
On a default install you will get a 301 redirect, and the Location header will read something like /author/yourlogin/.
That is the administrator’s username. Not the display name — the actual string used to log in. WordPress resolves the numeric user ID to the author archive, and the author archive is addressed by the login slug.
When I ran it on mine, the response was a clean redirect to my administrator account. Iterate the number and you enumerate every account that has ever published anything.
The second door: the REST API
WordPress exposes a users endpoint:
curl -s "https://yoursite.com/wp-json/wp/v2/users"
Unauthenticated, this returns a JSON list of users with their names and slugs. The slug is, again, effectively the login.
This endpoint exists for good reasons — the block editor uses it. But it does not need to answer anonymous requests on a site that has no public API consumers.
Closing them
The REST endpoint is the simpler of the two:
add_filter( 'rest_endpoints', function ( $endpoints ) {
if ( ! is_user_logged_in() ) {
unset(
$endpoints['/wp/v2/users'],
$endpoints['/wp/v2/users/(?P<id>[d]+)']
);
}
return $endpoints;
} );
Logged-in editors keep what they need. Anonymous requests get a 401.
The author archive is where it gets interesting, and where I got it wrong the first time.
My initial fix hooked template_redirect and returned a 404 for author requests. I tested it. It did not work — the redirect still fired and still leaked the login.
The reason is priority. WordPress core runs its own redirect_canonical on the same hook, and by default my handler was registered at the same priority, which means core’s ran first. It had already sent the redirect and exited before my code was reached.
function block_author_enum() {
if ( is_admin() ) { return; }
if ( ! isset( $_GET['author'] ) && ! is_author() ) { return; }
global $wp_query;
$wp_query->set_404();
status_header( 404 );
nocache_headers();
include get_query_template( '404' );
exit;
}
add_action( 'template_redirect', 'block_author_enum', 0 );
add_filter( 'author_rewrite_rules', '__return_empty_array' );
Priority 0 puts it ahead of core. The rewrite filter removes the author archive routes entirely, so the direct URL fails too.
Then I verified both paths again, because a security fix you have not re-tested is a security fix you are guessing about. ?author=1 returned 404. /author/myaccount/ returned 404. The rest of the site returned 200.
That verification step is not optional, and it is the step most often skipped. My first attempt looked correct in the code and did nothing in reality.
What else is standing open
Enumeration is the one people miss. These are the ones people know about and still leave.
XML-RPC. A legacy remote interface, used today mostly for brute-force amplification: one request can carry many login attempts. The PHP filter xmlrpc_enabled helps, but the request still reaches PHP. Block it at the server so it never gets that far.
PHP execution in the uploads folder. If an attacker gets a file into wp-content/uploads through any vulnerability anywhere, being able to execute it is what turns a bad day into a compromised server. Denying execution in that directory is the single highest-value server rule available.
Login error messages. By default WordPress tells you whether the username was wrong or the password was wrong. That is a free oracle for enumeration. Return one generic message for both.
The file editor in the admin. DISALLOW_FILE_EDIT means a stolen admin session cannot immediately become arbitrary code execution.
Uploadable file types. Use a whitelist. SVG in particular is not an image, it is an executable document that can carry script — allow it only if you sanitise it server-side and genuinely need it.
The form on your contact page
Since it is the one piece of code on most sites that accepts input from strangers, the order of operations matters and it is always the same:
Nonce, then bot trap, then rate limit, then sanitise, then validate, then act, then escape on the way out. Each check costs more than the one before it, and most junk never reaches the expensive end.
Two details that are routinely wrong. First, the return URL: if your form redirects to an address taken from the request, verify the host is yours, or you have built an open redirect that will be used in phishing that appears to come from your domain. Second, the sender address: put the visitor’s email in Reply-To, never in From. A message claiming to be from an address on someone else’s domain fails SPF and is filtered as forgery — which is the most common reason “our contact form does not work”.
Go and check yours now
Two commands, thirty seconds:
curl -sI "https://yoursite.com/?author=1" | head -n 5
curl -s "https://yoursite.com/wp-json/wp/v2/users" | head -c 300
If the first shows a redirect containing a name, or the second returns a list of users, your login is public. That does not mean you will be compromised. It means one of the two things an attacker needs is already done for them, and the other one is a list they downloaded years ago.
Both fixes are a few lines. The reason they are worth writing about is not that they are difficult — it is that they are default behaviour, invisible from the admin, and present on the overwhelming majority of WordPress sites running right now.