WordPress 2.5 Cookie Forging Explained

April 26, 2008 – 7:31 AM

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 

Where:

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:

admin0|1209331305|HMAC_FUNCTION("admin01209331305")

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:

admin|01209331305|HMAC_FUNCTION("admin01209331305")

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:

admin0|1209331305|HMAC_FUNCTION("admin0|1209331305")

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.

Source: Veracode

  1. One Response to “WordPress 2.5 Cookie Forging Explained”

  2. 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?

    By devnull on May 7, 2008

You must be logged in to post a comment.