Sep 14

It was discovered by Laurent Gaffie, who posted it to Full-Disclosure  http://seclists.org/fulldisclosure/2009/Aug/0113.html.  I quote from http://preachsecurity.blogspot.com/2009/08/wordpress-bugs-disturbing-vulnerability.html

From the disclosure, submitting a query into the server like so

http://domain_name.tld/wp-login.php?action=rp&key[]=

causes it to simply skip verification and just re-set the password… how interesting!

Under ordinary circumstances, a user clicks on “forgot my password” then the server sends a one-time URL complete with random “key” to re-set the password. The thing to remember here is that the field in the user’s record remains empty (null) until a password reset is requested! The user then gets an email with a URL that looks something like this:

http://domain_name.tld/wordpress/wp-login.php?action=rp&key=RANDOM0987654321

which he or she will click on. The server will then parse the request and compare the key parameter against the key stored in that user’s database record. Easy so far.

Guessing the key is probably not realistic and not worth the attack bandwidth… but if you could just reset passwords without knowing the key then that would be insanely annoying, wouldn’t it? The $64k question is… why does submitting a key[] (an empty array) work?

Looking at this issue it’s clear there are several things possibly at play, including a little bug that may plague more PHP apps than we give it credit for – casting. Looking at the very first relevant line:

$key = preg_replace(‘/[^a-z0-9]/i’, ”, $key);

it appears as though this looks and works just fine… as long as the input in the $key variable is a string the line goes about making sure you only have alpha-numeric values in the $key parameter… golden so far. If you happen to try and stuff an array in there (or worse, an empty array), as evidenced by the key[]= above, this all goes sideways. As you’ll now end up with an array in the $key variable… which the rest of the code is clearly not expecting.

Moving on… let’s look at the SQL query string that gets built…

$user = $wpdb->get_row($wpdb->prepare(“SELECT * FROM $wpdb->users WHERE user_activation_key = %s“, $key));

Aha! If you’ve got an array in the $key variable, and you try to run this select statement then you end up with a very ugly array being flattened and stuffed into the %s (string)… What happens here is that WordPress will flatten the array – take the first value of the key[] which will be null and shove it into %s… and then go off and perform its database query. It ends up ooking something like this:

SELECT * FROM $wpdb->users WHERE user_activation_key = null

Since the administrator has not requested a password change, the value of $key, in the admin record will be null… thus you have a match and a password reset takes place. Makes perfect sense… Whoops.

Now presumably this hits admin every time since this is the first user typically created in the database, but I’m just speculating now since I haven’t had a chance to thoroughly test this yet.

Even more interesting is the fix from the WordPress folks…

if ( empty( $key ) || is_array( $key ) )

The “patch” simply replaces the broken if empty statement and adds an is_array, which will check to see if we’ve accidentally left key blank or passed in an array and bomb if we have. Of course, this patches this bug but …

Mike Bailey (@mckt_ on Twitter) and I were poking at this and wondered… why wouldn’t you use “is_string” instead? Hasty coding? Why take the blacklist vs. whitelist approach?

Obviously, you have to wonder now… how many other bugs are there of this type in other PHP apps? What about other parts of WordPress? Hey… isn’t there a rumor going around that Kaminsky and Matasano got taken by an undisclosed WordPress bug? Hrmm… While I agree with Mike’s assessment that these types of bugs are rare, PHP certainly makes them more possible, especially when it doesn’t have the opportunity to throw errors in a situation like this.

I think Mike’s comment sums it up nicely though…

“It’s [the source] full of WTF moments… I’m amazed anybody uses WordPress”

Indeed.