Four ways a WordPress site hands out its usernames, and the curl checks for each

2026-11-03来源 dev.to SecurityEnglish

Before anyone guesses a password, they need a username. On a default WordPress site, getting one is easy, and it costs the attacker nothing to try. So the first thing I check on a site I'm handed is how many of its usernames it gives away for free.

There are four routes to look at. You can test each with curl from a machine that isn't logged in.

1. The author archive

Ask for an author by numeric ID and WordPress redirects you to that author's archive, which carries the login slug in the URL:

curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" "https://example.com/?author=1" 

A 301 to https://example.com/author/johnd/ just told you user ID 1 logs in as johnd. A bot walks the IDs up from 1 and collects every name that redirects.

The fix, if you don't publish author pages on purpose, is to drop author requests for logged-out visitors:

<?php add_action( 'template_redirect', function () { if ( is_author() && ! is_user_logged_in() ) { wp_redirect( home_url(), 301 ); exit; } } ); 

If you do run author pages, change the author base and the public slug instead of blocking them.

2. The REST API users route

curl -s "https://example.com/wp-json/wp/v2/users" | head 

A JSON array with id, name and slug for each author is the same leak in a tidier wrapper. Check the query form too, because a block that only matches /wp-json/ misses it:

curl -s -o /dev/null -w "%{http_code}\n" "https://example.com/?rest_route=/wp/v2/users" 

Drop the users routes for visitors with the rest_endpoints filter:

<?php add_filter( 'rest_endpoints', function ( $endpoints ) { if ( ! is_user_logged_in() ) { unset( $endpoints['/wp/v2/users'] ); unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] ); } return $endpoints; } ); 

3. The core sitemap

Since WordPress 5.5 the built-in sitemap has a users section:

curl -s -o /dev/null -w "%{http_code}\n" "https://example.com/wp-sitemap-users-1.xml" 

A 200 there lists your authors for anyone, and for search engines. Remove just that provider:

<?php add_filter( 'wp_sitemaps_add_provider', function ( $provider, $name ) { return ( 'users' === $name ) ? false : $provider; }, 10, 2 ); 

4. The login form

This one you read, not curl. Type a name that doesn't exist and WordPress says "The username is not registered". Type a real one with a wrong password and it says "The password you entered is incorrect". The difference confirms which names are real. Make both cases return the same generic message.

The one that undoes the other three

If your login is john and your byline on every post is also john, closing the three routes above buys you little. The name is printed on the page. Open your profile, set the public display name to something that isn't the login, and if you can, create the account with a login that was never a real name to begin with.

Why bother, if a username is only half a login

Because the other half is cheaper than people think once the name is known. The Verizon 2025 Data Breach Investigations Report put stolen credentials in 88% of basic web application attacks. A valid username turns a spray across thousands of accounts into a focused run at one, usually the administrator.

Run the four checks above on one site you manage today, then run them again after the fixes and keep the before-and-after. What else have you seen leak a WordPress username that isn't on this list?

原文:https://www.a8q9.cn/en/article/four-ways-a-wordpress-site-hands-out-its-usernames-and-the-curl-checks-for-each-2907b6.html

相关文章