Why WordPress Authentication Unique Keys and Salts Are Important

…or how to forge authentication cookies in WordPress

If you’ve ever installed or setup WordPress you should have surely seen your wp-config.php file, which contains the necessary configuration directives in order for WordPress to work. One section of the configuration file is dedicated to authentication keys and salts and this article will show you why you should keeps these safe and unique, regenerate these once in a while.

WordPress Authentication Keys and Salts

Salt, salt, salt… care to pass me the salt? Don’t! If I know your salt there’s a good chance I’ll be inside your WordPress administration panel within a week. Why? Because WordPress depends on the safety of these salts, once they are compromised the security behind authentication is relatively weak. But how?

If you don’t know or don’t remember what cryptographic salt is, feel free to brush up. The notion of a cryptographic hash function should also be familiar to you in order to better enjoy and understand this article.

So let’s get going. WordPress does not use PHP sessions to keep track of things like login state, it uses bare cookies. This means that much of the state information is stored on the client-side inside of these cookies. And client-side means that the “client” gets to play around a bit.

The authentication cookie, the name of which is stored inside of AUTH_COOKIE, which is formed by concatenating “wordpress_” with the md5 sum of the siteurl (default-constants.php#L142). This is the default behavior and can be overridden from inside your configuration file, by setting up some of the constants upfront.

The authentication cookie for this website (at the time of writing) is wordpress_7a3d03248fc2e614fc5e4b5aa9c560ed, with siteurl being “https://codeseekah.com”. That was easy and never meant to be hard.

The authentication cookie value is where the difficulty lies, obviously. The value is a concatenation of the username, a timestamp until which the authentication cookie is valid (can be any value in the future really), and an HMAC, which is sort of a key-biased hash for those who pulled of a TL;DR right now. The three variables are concatenated with the pipe character |.

username|timestamp|hmac
administrator|1334115331|f2fe6a78499771f39bbdeafcff1ab383

But wait, wouldn’t this cookie be constant, valid until the timestamp expires if all login credentials remain the same?

It certainly would. Morever, it is constant for a particular login, timestamp and set of keys. Authentication cookies cannot be destroyed until they expire. If an attacker gets their hands on a user cookie that has its expiration timestamp value set to 10 years in the future, as long as a subset of authentication keys and salts remain the same along with the password, they can attain login state without a password whenever they like for years to come.

So how would an attacker reconstruct this 128-bit HMAC in the cookie?

Lo and behold the default routine. Here’s how the HMAC is constructed:

$hash = hash_hmac('md5', $username . '|' . $expiration, wp_hash($username . substr($user->user_pass, 8, 4) . '|' . $expiration, $scheme));

We know the username (finding out WordPress usernames, and the verification thereof, is out of the scope of this article; but stay tuned, I’ll write a little something on timing attacks for username validation in WordPress later this week or early next week), we know the scheme is auth or secure_auth based on whether WordPress is in SSL mode at the time of the attack, expiration is any valid UNIX timestamp that is in the future, and given the knowledge of the necessary key and salt (AUTH_KEY and AUTH_SALT) the defined wp_hash and wp_salt functions are both predictable.

What this leaves us with is a mere 16,777,216 combinations to try and guess a mere 4 characters of the user’s password hash in the following 64-character keyspace: ./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz (as defined by PHPass).

Bruteforcing is relatively effortless. Send HEAD or GET requests to wp-login.php with the cookie and look for a 302 response code (this page redirects logged in users to the index of wp-admin, meaning we’re in). To generate less suspicion in server logs other pages, including ones on the front-end can be checked against (the Admin bar, Logout, edit links, etc.). An attacker with a single machine that does around 30 request per second can exhaust all the possibilities in a week (as long as the server can keep up), no CAPTCHAs, fancy login routines, nonces, pure lightweight bruteforcing. You’ll have to be a worthy target, though, no casual bot sweep will ever exploit this. So I wouldn’t panic.

Now you can see why I said it was relatively easy, since bruteforcing a 128-bit hash (HMAC MD5) would require up to 3.4*10^38 tries (the hash bits themselves, not the input). Bruteforce that! However, knowing the keys reduces that number of possible hashes by 2*10^31 times. Yes it’s 200,000,000,000,000,000,000,000,000,000,000 times easier to forge the WordPress authentication cookie if you know the keys.

Once you’re in, WordPress will greet you with some extra cookies that you get to keep as well.

WordPress Authentication Key Salt Cookies

Attaining the authentication keys themselves is outside of the scope of this article, there are dozens of attack vectors, including social engineering, since the keys look just a bunch of crap and nowhere does it say that you shouldn’t share them with anyone. Tweet me yours, the Internet is all about sharing :D.

Mitigation

Protecting yourself is quite easy.

  • Pick strong unique keys, just like passwords, but the benefit of it all is that you don’t have to remember them. Always set them in you configuration file.
  • Change keys every now and then. One of the WordPress.org APIs generates authentication keys and salts and you’re free to use it. It doesn’t take more than a minute to change these via copy and paste. All compromised cookies are suddenly invalidated.
  • Do not let go of the keys, nobody will ask you for these keys, there are of absolutely no external value.
  • Set the COOKIEHASH value to something less predicatable in your configuration file, that way the name of the cookie itself will pose a problem to attackers.
  • Increase entropy, explained below.
  • Keep your server safe, patched, etc. common sense approach to protecting your files, WordPress and the server.

Increasing entropy

Increasing entropy from 16,777,216 possible HMAC hashes is fun and easy. All the functions are pluggable. Plug the wp_hash function and add something unpredictable:

function wp_hash($data, $scheme = 'auth') {
  $salt = wp_salt($scheme) . 'PREDICT THIS, BRO!';
  return hash_hmac('md5', $data, $salt);
}

Ouch, suddenly there is 128-bits of entropy, given that your function is invisible to the outside world. That string can come from anywhere else (think remote, database, request variables). Make sure you know what you’re doing though, hashes are generated for many things inside WordPress so the above code will probably mess things up.

I wouldn’t expect the WordPress development team to increase entropy out of the box for us WordPress fanatics, even if it would take a 1-character change in two functions to increase the length of the user password hash fragment from 4 characters to say 5, which increases the number of tries up to 1,073,741,824, which will take 30 days at 60 tries per second non-stop, making the authentication cookie forging attack vector less attractive.

Unless your WordPress dashboard provides direct communication with a nuclear C&C center I wound’t worry about this all anyway. There are million and one and other ways to get in, which brings be to the…

…Conclusion

Stay safe. Over and out. 🙂