Blog

Immediately Banning more than just “admin”

If you’ve already repaired this feature using the technique discussed in the previous post, your new concern becomes obvious: users regularly attempt other names they assume to be administrator usernames as well and those guesses are pretty obvious.  Programming for the majority of them should be fairly easy.

The most frequent guessed usernames I see are “www.username.com” and “username.domain.com” (where “username” is the guess).  I’m not expecting you to be a RegEx expert here, but this is the easiest tool I know of to match both conditions.  There are also a number of really obvious alternatives to “admin”.  “Admin” is an obvious choice since the downloaded WordPress setup offers it up as the default username when installation is first run.  This also makes it the worst choice for an actual username.  I explicitly forbid its use on my installs, and strongly urge you to do the same in yours.

As always, use these modifications at your own risk.  Make backups of your site files before making these changes.  It would be wise to backup your database before proceeding also.  As before, if you make this change, you’ll undo the change with every update to the iThemes plugin, so expect to have to follow these steps again.

File: wp-content/better-wp-security/core/modules/brute-force/class-itsec-brute-force.php

We’ll modify the authenticate function in this file.  Note: if the IP address you’ll be logging in from is always whitelisted, you can safely add your own username as one that is blacklisted when we modify this function.

First, the oddest part is the RegEx.  The one I’m using here will match anything after http:// or http://www.  up to any character that is not a dash, letter or number. (example: http://www.this-is-a-match.com, http://so-is-this.domain.com, http://and-so-is-this.com) This specifically breaks down like this:

\d – any digit
a-z – any lowercase letter
A-Z – any uppercase letter
“-” – A dash.

The name you’re looking for should be within that character set as I think this is necessary in order to be a valid domain name. Starting at or around line 39:

public function authenticate( $user, $username = '', $password = '' ) {

	global $itsec_lockout, $itsec_logger;

	$site_url = get_site_url();
	$url_match = '/^https?:[?:www\.]?\/\/([\da-zA-Z-]+)/i';
	$url_is_match = preg_match($url_match, $site_url, $the_match);

Side note: another attack I see is for sites with multiple parked domain names. Instead of using the true site’s domain as a username, they’ll use each of those, which could be quite numerous. I find it odd since… this data is unreliable at best, and is even spoof-able. However, the “referral” variable seems to always be set properly. You could use the same technique to check the referring address for a match against your banned username list.

At this pint, $the_match is an array loaded with matches.  The first element is the entire string, the second is the piece we want ($the_match[1]).  There is a very tiny chance the RegEx didn’t work, or didn’t work properly.  Not sure, this should be a very tiny chance that some alteration in server configuration might change how our RegEx is evaluated, so I check to make certain a match was found before doing anything with the value.  Anyway, the next part here, we’ll just set up an array including all the names we want blacklisted:

	$banned_names = array(
		"admin",
		"administrator",
		"test"
	);
	if ($url_is_match) { // in case something went wrong
		$banned_names[] = $the_match[1];
	}

I’m using an array here for your list of names primarily because the RegEx string becomes a nightmare to look at and to edit after you have about 3 names in the list. Now, we’ll check those names against the username that was attempted during login:

	$banned_user_names = "/^(".implode("|",$banned_names).")$/i";
	$ban_match = preg_match($banned_user_names, $username);

This may seem a bit complex, but it’s really not. We’re just constructing the RegEx string with all this so that it looks like this: “/^(name1|name2|name3)$/i” … which matches any of the following names: “name1”, “name2” or “name3”. The ^ and $ ensures the name is matched as the whole word, the vertical bar (|) is RegEx code for “or”, and the “i” after the forward slash (a delimiter) is just a flag that lets it know this should match any letter case. After these two lines of code run, $ban_match will either be NULL or a numeric value of how many matches were found (so NULL or 1).

Now look for this part (originally at or around line 43):

	//Look for the "admin" user name and ban it if it is set to auto-ban
	if ( isset( $this->settings['auto_ban_admin'] ) && $this->settings['auto_ban_admin'] === true && 'admin' === $username ) {

Modify to this:

	//Look for the "admin" user name and ban it if it is set to auto-ban
	if ( isset( $this->settings['auto_ban_admin'] ) && $this->settings['auto_ban_admin'] === true && $ban_match ) {

So we’re just switching from if $username == ‘admin’ to if $username has a RegEx match with any number of blacklisted names. This – in combination to the previous fix to this feature means an immediate ban of any IP address attempting one of the names you specify.

To keep your installation even MORE secure, right now would be a great time to enable the Clef option to prevent logging in with a username and password for all users with Administrator access and add those administrator usernames to this ban array. At that point anyone trying to log in with your actual administrator usernames will be immediately banned from the site, but your administrators will still be able to use Clef to login without issue.

Now that you have a blacklist for admin names and immediate banning, wouldn’t it be great if you had a file blacklist? So that you could bypass the typical 404 behavior and just immediately ban when certain – clearly malicious – files are attempted to be accessed? Tune in next time…