Tuesday, December 16, 2008

Classic bug: phpBB 2.0.12 authentication bypass

December is always a very busy month at work, so it has been a while since my last post. Here's another classic bug: the phpBB 2.0.12 authentication bypass. I am not sure who discovered this bug, but the first references to it seem to be from February 2005.

As the title says, this is an authentication bypass bug, which means an attacker can access any account on a vulnerable phpBB version without knowing the password. This is not your typical web application bug though, it has to do with serialization and you could patch it by adding only one byte to the vulnerable code.

So how does it work? A phpBB forum has a ‘remember me’ function, which basically gives you a cookie which automatically logs you in. The cookie looks something like this:

a:2:{s:11:"autologinid";s:32:"96948aad3fcae80c08a35c9b5958cd89";s:6:"userid";s:4:"3207";}

This format is created by the PHP serialize function. Basically, it is a technique to store a complex structure into a string. If we unserialize the above example, we get this (print_r output):

Array
(
[autologinid] => 96948aad3fcae80c08a35c9b5958cd89
[userid] => 3207
)

So our cookie is used to store an array containing two strings: our userid and something called an ‘autologinid’. As it turns out, the autologinid is just an MD5 hash of the phpBB password. Not such a good idea, as this makes phpBB vulnerable to passing-the-hash attacks. phpBB 3 doesn’t use this type of cookie anymore though.

The bug is in this part of the phpBB source code (simplified):

$sessiondata = unserialize(stripslashes($HTTP_COOKIE_VARS[$cookiename]));
if( $sessiondata['autologinid'] == $userdata['user_password'] ){
// autologinid matches password
$login = 1;
}

Doesn’t look that bad does it? After unserializing the data, It simply compared the autologinid in our session with the MD5 of the user’s password. They use the == operator to compare two strings, nothing wrong with that. But what if one of the operands in the comparison is not a string? Take for example the following PHP code:

if(true == ‘96948aad3fcae80c08a35c9b5958cd89’){
echo ‘true!’;
}

You may not expect it, but this piece of code outputs ‘true!’. PHP simply converts the string to a boolean and unless the string is empty or ‘0’ PHP will convert it to ‘true’. So what does this have to do with our vulnerability? Let’s take another look at a part of our serialized cookie:

s:32:"96948aad3fcae80c08a35c9b5958cd89"

Our MD5 hash is prefixed with s:32:, which means this variable is a string with a length of 32. So instead of just specifying the string itself, we also specify its type! The trick is to simply change this part of our cookie to:

b:1

Which makes our cookie:

a:2:{s:11:"autologinid";b:1;s:6:"userid";s:4:"3207";

For the unserialize function, this simply means: the value of autologinid is a boolean with the value ‘true’. The next step is to change our cookie on the forum to this one. The forum will compare the password hash of the user with userid 3207 to ‘true’, tricking phpBB into thinking we have the actual password hash! A logical next step would be to change the userid to '1', which is the userid of the forum administrator user, giving us full control over the forum.

The patch for this vulnerability whas a simple one. The phpBB team simply changed the line:
if( $sessiondata['autologinid'] == $userdata['user_password'] ){
to
if( $sessiondata['autologinid'] === $userdata['user_password'] ){

In other words, they switched from the == to the === operator. The PHP manual has the following description for the === operator: “TRUE if $a is equal to $b, and they are of the same type.”. The fix was this simple, by changing the operator, the two compared types have to be equal, so an attacker can no longer supply a boolean instead of a string and bypass authentication.

The moral of this story? Every piece of input is important, don’t allow users to supply serialized data, unless absolutely necessary!