Mike Adams on Three Security Issues You Thought You’d Fixed

Mike Adams spoke about Three Security Issues You Thought You’d Fixed at WordCamp San Francisco 2013. Although this was at a WordPress event, the security issues he talks about apply more broadly to PHP and web sites in general.

I work with Mike at Automattic and we are hiring. If you enjoy bunny puns you should check out our list of open positions.

WordCamp Salt Lake City 2013

WordCamp Salt Lake City is back for 2013!

This year we are moving the location to Sandy, Utah, which should make it easier for our friends in Utah county. The big day is Saturday 21 September 2013.

Do you have a WordPress topic that you are passionate about? Submit a presentation for consideration. Want to help us cover the bills? We have sponsorship opportunities too.

Registration is now open, with discounted early bird pricing.

WordCamp Salt Lake City has a history of bringing local ( and some not so local ) WordPress users, developers, designers and fans together in one spot on one day. I look forward to seeing everyone again.

Stateless CSRF Tokens

UPDATE: I posted improved versions of these functions in Better Stateless CSRF Tokens.

I’ve been thinking about CSRF tokens lately. If you are using the built in $_SESSION feature of PHP a common pattern goes something like this ( similar to what Chris Shiflett describes ) :

session_start();

...

$token = base64_encode( openssl_random_pseudo_bytes( 32 ) );
$_SESSION['token'] = $token;
$_SESSION['token_time'] = time();

...

echo '<input type="hidden" name="token" value="' . $token . '" />';

...

if ( 
    isset( $_POST['token'] )
    && $_POST['token'] === $_SESSION['token'] 
) {
    if ( ( time() - $_SESSION['token_time'] ) <= 300 ) {
        // valid token, within time limit
    }
}

A few notes about this approach. First, use openssl_random_pseudo_bytes instead of mt_rand ( suggested by Kevin Schroeder ) when possible. Second, be sure to only use === when comparing the token value. You want to avoid automatic type juggling.

Why worry about automatic type juggling when comparing CSRF tokens? Try this script:

$token = 'abc123';
$form_token = 0;

if ( $form_token == $token ) {
    echo "Valid token\n";
} else {
    echo "Invalid token\n";
}

Even though $token and $form_token clearly don’t contain the same values this script will display ‘Valid Token’ because of automatic type juggling. That makes your CSRF token basically useless as an attacker can set the token to zero and it will be considered valid. Switching to === will display the expected ‘Invalid token’.

This is all fine and good until you want to avoid using PHP sessions. Perhaps you have several web servers and don’t want to deal with shared session storage. Or have servers in multiple data centers and don’t want to try and sync state across them. What ever the reason, popping a token into $_SESSION isn’t an option in this case. In short you want some sort of stateless CSRF token.

One method is to generate a token based on known values that won’t change and lasts for a given period of time. This is what WordPress does. You can see the WordPress implementation in the inaccurately named wp_create_nonce and wp_verify_nonce functions ( WordPress nonces aren’t really nonces, they can be used more than once ). The high level version is that WordPress takes a known set of values like the user id, NONCE_KEY, descriptive action text, and current time; then runs them through an MD5 HMAC.

By default the tokens are good for 24 hours. You can adjust the time a WordPress nonce value is valid for by filtering nonce_life. That isn’t very flexible though. If you want to use different HMAC keys and timeouts across various requests then you end up having to filter both sides ( create and verify ).

I got to wondering what a more flexible approach to stateless CSRF tokens would look like. Here is what I’m thinking of:

function request_token_generate( $data_str, $key, $timeout = 900 ) {
    $now = microtime( true );
    $hash = hash_hmac( 'sha256', "$data_str|$now|$timeout", $key );

    return base64_encode( $hash ) . "|$now|$timeout";
}

function request_token_verify( $token, $data_str, $key ) {
    list( $hash, $hash_time, $timeout ) = explode( '|', $token, 3 );
    if ( empty( $hash ) || empty( $hash_time ) || empty( $timeout ) ) {
        return false;
    }

    if ( microtime( true ) > $hash_time + $timeout ) {
        return false;
    }

    $hash = base64_decode( $hash );
    $check_hash = hash_hmac( 
        'sha256', "$data_str|$hash_time|$timeout", $key 
    );

    if ( $check_hash === $hash ) {
        return true;
    }

    return false;
}

For request_token_generate you’ll need to provide a string containing unique data about the user and request, an HMAC key value, and an optional timeout. One example of the data string would be a combination of the internal user id, descriptive text about the action being taken, timestamp of when the password on the account was last changed, and the last 6 characters of the hashed password. Depending on the flow of the request you might want to include the URL that the form is expected to be on and the remote client IP address.

There is no specific limit on what you could include in the data string. Anything likely to be fairly unique to that user and request would be a good candidate. For that mater you could generate an additional unique key for each user at signup time that could be included.

$key = '5up3R53cr3T!';
$data_str = '45873' . 'delete_post_345' 
    . '2013-05-01 14:45:32' . '2dH6hi';
$request_token = request_token_generate( $data_str, $key );

echo "Token: $request_token\n";

That will produce a token that looks something like:

MTljOWZmNzhmOTY5Y2Y5Y2IxNjdkNzQ5YzVkYTcwNzMyMzNjMjhmNTdmZTFhZjVkNmEwNTAyMmFjMjBmMTExMQ==|1373336147.4861|900

This is really 3 values separated by |, the first is the base64’d HMAC ( using SHA256 instead of MD5 ). The second value is the timestamp for when the token was generated and the third is how long the token is good for in seconds. Verifying the token is easy enough:

if ( request_token_verify( $request_token, $data_str, $key ) ) {
    echo "Valid token\n";
} else {
    echo "! INVALID ! token\n";
}

The verification side needs to have access to the same values that went into building $data_str and the HMAC key. You don’t need to know the timeout for the token because the timeout is included as part of the token value.

This approach prevents tampering by including the timestamp and the time out values as part of the HMAC call ( as does WordPress ). Testing this is easy enough:

$key = '5up3R53cr3T!';
$data_str = '45873' . 'delete_post_345' . '2013-05-01 14:45:32' . '0dH6hi';
$token = request_token_generate( $data_str, $key, 15 );

// confirm original works
echo "Should be valid: ";
if ( request_token_verify( $token, $data_str, $key ) ) {
    echo "Valid token\n";
} else {
    echo "! INVALID ! token\n";
}

// turn back time
list( $hash, $hash_time, $timeout ) = explode( '|', $token, 3 );
$hash_time = $hash_time - 100;
$fake_token = "$hash|$hash_time|$timeout";

echo "Should be INVALID: ";
if ( request_token_verify( $fake_token, $data_str, $key ) ) {
    echo "Valid token\n";
} else {
    echo "! INVALID ! token\n";
}

// alter timeout
list( $hash, $hash_time, $timeout ) = explode( '|', $token, 3 );
$fake_token = "$hash|$hash_time|10000";

echo "Should be INVALID: ";
if ( request_token_verify( $fake_token, $data_str, $key ) ) {
    echo "Valid token\n";
} else {
    echo "! INVALID ! token\n";
}

The first test checks that the unmodified token is valid. The second test attempts to set the timestamp back in time. The third attempts to increase the timeout value. The two altered tokens fail because the hash values no longer match.

Overall I’m happy with this approach to stateless CSRF tokens.

WordPress 10th Anniversary Party in Utah

You might have heard about WordPress turning 10 years old on May 27th. To celebrate local WordPress communities around the world are having anniversary parties on May 27th, 2013. This includes the greater Salt Lake City, Utah area. The details are at http://www.meetup.com/WordPress/Salt-Lake-City-UT/930892/:

When: Monday, May 27, 2013, 7:00 PM
Where: Sonny Brian’s; 33 East 11400 South in Sandy


View Larger Map

If you are coming be sure to RSVP so that we have an idea of how many people to expect. Bluehost is also giving away the 10th Anniversary WordPress t-shirts to the first 30 people who fill out this form on wpslc.com.

It will be a fun time to hang out and chat with other local WordPress fans/users/designers/developers.

Code Garage Migration to VaultPress

Today VaultPress announced the Code Garage migration details. We really wanted to make sure that we had the details and options on this right. I know that migrations like this can often be annoying, so we went out of our way to make the process smooth and inviting.

Code Garage users that migrate to VaultPress will get their first two months on VaultPress free. For those who don’t want to migrate, we’ll refund your last payment.

Even if you aren’t a Code Garage customer, you should go read Peter’s CodeGarage Locker is Migrating to VaultPress post. He gives a personal history of how Code Garage came to be, how it grew, and how it ultimately was sold to Automattic.