Plugin Sale! Save 15% on pro plugins with discount code: FALL2020
Web Dev + WordPress + Security

Stop User Enumeration in WordPress

[User Enumeration ] This tutorial explains how to block user-enumeration scans in WordPress. As explained in greater depth here, user enumeration happens when some malicious script scans a WordPress site for user data by requesting numerical user IDs. For example, requests for author=1 through some number, say, author=1000, may reveal the usernames for all associated users. With a simple enumeration script, an attacker can scan your site and obtain a list of login names in a matter of seconds.

How it works

When scanning a site for user IDs, disclosure of user data happens in two ways. First, for permalink-enabled sites, requests for ?author=n (where n equals any integer) are redirected to the permalink version of the URL for that user, which by default includes the author’s login username. So for example, on a permalink-enabled site, the following URI requests:

http://example.com/?author=1
http://example.com/?author=2
http://example.com/?author=3
.
.
.

..automatically are redirected by WordPress to their “pretty permalink” counterparts:

http://example.com/author/admin-user/
http://example.com/author/wordpress-user/
http://example.com/author/some-other-user/
.
.
.

..of course, the actual usernames will vary depending on your site, but you get the idea.

The second reason that user enumeration works to reveal user data is that theme templates typically display the author name on author-archive pages, in post meta information, and possibly in other locations, depending on the theme.

For more details on user enumeration, check out my article on blocking user ID phishing requests over at htaccessbook.com.

Should you be worried?

If you are sure that all of your users are using strong passwords that are updated regularly, then there is nothing to worry about. This tutorial is aimed at sites with multiple authors who may not be “password savvy”. If an author is being lazy with their passwords, then user-enumeration could definitely put your site at risk. Equipped with a known username, a perpetrator quickly may gain access using a simple brute-force attack.

So to be safe, check out the following techniques to protect your site against user-enumeration and brute-force attacks. They take only a minute to implement, and will serve to harden your WordPress-powered site with additional layers of security.

Step 1: Disable the scans

The first thing we want to do is block the malicious enumeration scanning. This can be done in one of two ways:

  • Add a code snippet to your theme’s functions.php file
  • Add a code snippet to your site’s root .htaccess file

Let’s check out each of these methods..

Block user-enumeration via functions.php

To block user-enumeration via functions.php, add the following code to your theme’s functions file:

// block WP enum scans
// https://m0n.co/enum
if (!is_admin()) {
	// default URL format
	if (preg_match('/author=([0-9]*)/i', $_SERVER['QUERY_STRING'])) die();
	add_filter('redirect_canonical', 'shapeSpace_check_enum', 10, 2);
}
function shapeSpace_check_enum($redirect, $request) {
	// permalink URL format
	if (preg_match('/\?author=([0-9]*)(\/*)/i', $request)) die();
	else return $redirect;
}

No editing is required for this to work, just copy/paste and done. Here’s how it works:

  1. Check if the request is for any page in the WP Admin Area
  2. Block the request if it’s for a query-string author archive

That’s the basic gist of it. Hit me up in the comments section for more specifics on what this code is doing, how it works, etc.

Block User Enumeration via .htaccess

If you would rather block requests at the server level, you can add the following slice of .htaccess to your site’s root .htaccess file:

# Block User ID Phishing Requests
<IfModule mod_rewrite.c>
	RewriteCond %{QUERY_STRING} ^author=([0-9]*)
	RewriteRule .* http://example.com/? [L,R=302]
</IfModule>

The only edit that’s required is the domain/URI, http://example.com/, which you should change to match your own. For more information about this technique, check out my tutorial on blocking user-id phishing.

FYI: You can also stop user enumeration with BBQ Pro Firewall »

Step 2:

At this point, we’ve added a code snippet (in either functions or .htaccess) that will block those nasty user-enumeration scans. The second part of the equation is to make sure that your theme does not disclose the login username of any authors or users. Unfortunately, there is no quick, one-step solution for this step, as it requires careful examination of your theme. Here are some things to check:

  • Author name displayed for each post
  • Author name displayed for author-archive views
  • Author name displayed anywhere else on the front-end

If your theme displays author names anywhere (as most themes do), there are few ways to prevent username disclosure:

  • Change all user Display Names to anything other than the login name
  • Make sure any author/user template tags are not displaying the login name
  • Remove any template tags that display author/user login names
  • Disable author archives entirely (if not needed)

Of course, this is a general guide that may not be applicable to every theme on the face of the planet (there’s only like a billion of them). But it should be enough to give you the idea and help you implement the best possible solution for your site.

Jeff Starr
About the Author
Jeff Starr = Fullstack Developer. Book Author. Teacher. Human Being.
BBQ Pro: The fastest firewall to protect your WordPress.

12 responses to “Stop User Enumeration in WordPress”

  1. Rick Beckman 2016/06/30 6:52 pm

    Hey, Jeff, excellent tip as always. Do you plan to incorporate this in the next generation of your blacklist? I don’t always understand everything in those blacklists, and when upgrading to the eventual 7G, I’m curious if I’ll end up with redundant blocking at that point, which I suppose is a minor thing in the grand scheme of things. Haha.

    • Jeff Starr
      Jeff Starr 2016/07/01 9:47 am

      Hey Rick, I like the idea but this sort of technique is best applied at the site level. Mostly because the 6G/7G are aimed at a broader user base, which includes non-WP sites and WP sites that aren’t using permalinks.

  2. Hi Jeff,

    thanks for the tips!

    I disable the author archives with the option that the plugin Yoast SEO offers. If you do so, you get a 404 page which is not cached because of the query string. So I think the .htaccess rule is the most efficient way to block brute force attacks.

    It becomes more complicated if you like to use the author archive pages. The author URL is using the value for “user_nicename” and this is by default exact the same as the login name. For just a few authors you can change the value via the database, but that is not a solution for sites with many authors or users.

  3. Forgive me if i’very read it wrongly,but use of is_admin is a common coding slip up for checking user permissions (and I think worth highlighting – i’m guilty of the same error). From the codex:

    “This Conditional Tag checks if the Dashboard or the administration panel is attempting to be displayed. It is a boolean function that will return true if the URL being accessed is in the admin section, or false for a front-end page.”

    • Jeff Starr
      Jeff Starr 2016/07/28 8:29 am

      Not a slip-up in this case, as we are simply checking if the request is for any page in the WP Admin Area. Not trying to check if the user is logged in. I too have made that mistake in the past, but for this technique is_admin is correct. Thanks for the feedback.

      • Nick the Geek 2016/09/26 12:20 pm

        I think the issue is with this statement

        > Check if the request is made by a user with admin-level capabilities

        That appears to be describing the is_admin() function as checking the user cap, which is does not. The code is correct, but the description of what it is doing is not.

        Related, I’m wondering why you are doing a preg_match on $_SERVER['query_string'] instead of just checking to see if isset($_GET['author'])?

      • Jeff Starr

        Thanks Nick, you are correct about the description — I have updated the article accordingly.

        For checking the author parameter, I wanted to keep the script as focused as possible to avoid any possibility of false positives. There may be plugins/themes that make use of the author parameter on the frontend, so I didn’t want to interfere with anything. Just checking if the author is set may cause issues. That said, I should probably rephrase the snippet to use stripos instead of the more costly preg_match.

  4. Bojo Negoro 2016/08/13 7:38 pm

    I got a lot of brute force attempt reported by security plugin I used. So, that thing above maybe very helpful to me. But, manually edited “things” are difficult to me, maybe you can mention a plugin that handle those work?

  5. Omar Abrahim 2016/09/28 9:02 am

    Hi Jeff,

    If I wanted to stop user enumeration in the functions php file, as you mention above, could I modify the code to redirect any ?author= attempts right back to the home page? Or does not that make sense? If so, how would I do that exactly? Please let me know. Thanks.

Comments are closed for this post. Something to add? Let me know.
Welcome
Perishable Press is operated by Jeff Starr, a professional web developer and book author with two decades of experience. Here you will find posts about web development, WordPress, security, and more »
WP Themes In Depth: Deep dive into WP theme development.
Thoughts
Watching. Waiting. Praying.
Got all of my free WordPress plugins updated for imminent WP 5.6 in early December. Pro plugin updates currently in the works.
7G Firewall now integrated into BBQ Firewall (free version). Pro version soon ;)
macOS Big Sur update complete. So far no crazy issues. Except TextEdit, which is completely screwed up and unusable. Replaced with free BBEdit.
Got so sick of macOS’ annoying “red dot” that I had to remove System Prefs from the dock. Come on Apple you can do better.
Beginning development of an Nginx version of 7G Firewall.
Happy Birthday to Perishable Press, celebrating 15 years online! :)
Newsletter
Get news, updates, deals & tips via email.
Email kept private. Easy unsubscribe anytime.