Latest TweetsVerify any search engine or visitor via CLI Forward-Reverse Lookup perishablepress.com/cli-forwar…
Perishable Press

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 = Creative thinker. Passionate about free and open Web.
Archives
12 responses
  1. Rick Beckman June 30, 2016 @ 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

      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

      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 September 26, 2016 @ 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 August 13, 2016 @ 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 September 28, 2016 @ 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 ]