WordPress 2.5.1 came out recently. It includes a critical security fix for a cookie integrity bug that would allow an attacker to impersonate other users, including WordPress admins, by manipulating the contents of an HTTP cookie. Whenever I read about a vulnerability predicated on the user identity being embedded into a client-side token (as opposed to a pseudorandom session identifier), I like to dig a little deeper to see what's going on.

How does the authentication mechanism work?

The advisory describes the structure of the WordPress authentication cookie as follows:

The new cookies are of the form:

"wordpress_".COOKIEHASH = USERNAME . "|" . EXPIRY_TIME . "|" . MAC 


COOKIEHASH:  MD5 hash of the site URL (to maintain cookie uniqueness)
USERNAME:    The username for the authenticated user
EXPIRY_TIME: When cookie should expire, in seconds since start of epoch
MAC:         HMAC-MD5(USERNAME . EXPIRY_TIME) under a key derived
             from a secret and USERNAME . EXPIRY_TIME.

So you login to WordPress with your username and password, and then the login page issues you a cookie such as the one below:

Set-Cookie: wordpress_52440d615a927011d57374216b3ff789=
  admin%7C1209329209%7C7d5e9e67d8f74a2b657b2e63437a1241; path=/blog/

As expected, the cookie contains the username, expiration in epoch time, and an MD5 hash (the %7C's are the URL-encoded form of the '|' character). The wp_generate_auth_cookie() function generates the cookie as follows:

$key = wp_hash($user->user_login . $expiration);
$hash = hash_hmac('md5', $user->user_login . $expiration, $key);
$cookie = $user->user_login . '|' . $expiration . '|' . $hash;

Each subsequent request that your browser makes to WordPress contains the authentication cookie, which the software then verifies to make sure you are who you say you are. This occurs in the wp_validate_auth_cookie() function:

list($username, $expiration, $hmac) = explode('|', $cookie);
$key = wp_hash($username . $expiration);
$hash = hash_hmac('md5', $username . $expiration, $key);

if ( $hmac != $hash )
  return false;
$user = get_userdatabylogin($username);

As you can see, the function parses out the username, expiration, and HMAC from the cookie. It then generates the expected hash value, based on the concatenation of username and expiration, and compares it to the HMAC in the cookie. If the values match, it fetches the user object corresponding to the username in the cookie, and you're authenticated.

So how can this be attacked?

The authentication mechanism assumes that an attacker cannot calculate the HMAC. However, this assumption is broken because the two inputs used to calculate the HMAC (username and expiration) are not clearly delineated. To illustrate, let's say I register a new user account and for the username, I select admin0. Now I login to the application and I get back my authentication cookie, which would look something like:


Do you see where we are going with this? Now that we know the expected value of HMAC_FUNCTION for the string "admin01209331305", we can re-use it to our advantage. We simply remove the 0 from the end of the username and prepend it to the expiration time, keeping the HMAC value the same:


The HMAC calculation checks out, and as far as WordPress is concerned, you've just authenticated as the admin user.

How was it fixed, and does it matter to me?

The fix was straightforward: use a delimiter to ensure there is no ambiguity between the username and the expiration when calculating the HMAC. Now when I login as the admin0 user, my cookie looks like this:


You can't re-use the calculated HMAC_FUNCTION for "admin0|1209331305" in any useful way, because there's no longer ambiguity between the username and expiration values.

As stated in the original advisory, you should upgrade ASAP if your WordPress instance is configured to permit account creation.

About Chris Eng

Chris Eng, vice president of research, is responsible for integrating security expertise into Veracode’s technology. In addition to helping define and prioritize the security feature set of the Veracode service, he consults frequently with customers to discuss and advance their application security initiatives. With over 15 years of experience in application security, Chris brings a wealth of practical expertise to Veracode.

Comments (6)

Amana | May 3, 2008 4:00 pm

Hello. MY wp is also 2.5, but cookie looks like :
Why it is different to
admin0|1209331305|HMAC_FUNCTION("admin0|1209331305") ???

CEng | May 5, 2008 12:47 pm

@Amana: Replace each occurrence of %7C in your cookie with the '|' (pipe) character and you will see that yours does look similar to what I've described. Your expiration timestamp is 1211057307 and the result of the HMAC is 6e0e9bec639167e4b2cfde09892e9d08.

Paul @ Web Design Ireland | May 5, 2008 4:47 pm

Thanks for explaining this. Im a great believer that security disclosure helps make us better developers.

devnull | May 7, 2008 4:36 am

I did some backtracking and I must be missing something or I have misunderstood something because this all seems imposible without knowing SECRET_KEY and SECRET_KEY constants if those aren’t defined a random string is generated.
$key = wp_hash($user->user_login . $expiration); great however
function wp_hash($data) { $salt = wp_salt();
lets have a look at wp_salt();

function wp_salt() {
global $wp_default_secret_key;
$secret_key = ”;
if ( defined(’SECRET_KEY’) && (” != SECRET_KEY) && ( $wp_default_secret_key != SECRET_KEY) )
$secret_key = SECRET_KEY;

if ( defined(’SECRET_SALT’) ) {
$salt = SECRET_SALT;
} else {
$salt = get_option(’secret’);
if ( empty($salt) ) {
$salt = wp_generate_password();
update_option(’secret’, $salt);

return apply_filters(’salt’, $secret_key . $salt);

So without knowing those constants how can you feed function hash_hmac($algo, $data, $key, $raw_output = false) the param $key?

CEng | May 8, 2008 4:20 pm

@devnull: You're missing the point. I don't have to feed all the parameters into the HMAC function. It doesn't matter if I know SECRET_KEY or not because at no point am I (as an attacker) required to provide the key. The point is that the HMAC function will calculate the same value for [username=admin, expiry=01209331305] as it will for [username=admin0, expiry=1209331305]. I can use WordPress to do that calculation for me -- legitimately -- and then re-use that value.

This is sometimes referred to as an oracle attack. We use the application against itself by asking it to perform a useful cryptographic operation for us -- in this case, calculating an HMAC for one user that we can then use to impersonate another user. We don't care what the key is because we have a valid HMAC and that's all that matters.

Crossing Arbogast | July 9, 2009 1:17 am

Thanks for this Nice post, Really usefull all of us. just bookmarked this post in my digg profile, hope you will update more post soon.
I really liked your blog!


Please Post Your Comments & Reviews

Your email address will not be published. Required fields are marked *

Love to learn about Application Security?

Get all the latest news, tips and articles delivered right to your inbox.