Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding the Wordpress vulnerability

A vulnerability has recently been disclosed that affects WordPress 2.8.3 and allows the admin user to be locked out of their account by changing the password.

This post on Full Disclosure details the flaw, and includes relevant code snippets. The post mentions that 'You can abuse the password reset function, and bypass the first step and then reset the admin password by submiting an array to the $key variable.'

I'd be interested in someone familiar with PHP explaining the bug in more detail.

Those affected should update to a new 2.8.4 release which apparently fixes the flaw.

wp-login.php:
...[snip]....
line 186:
function reset_password($key) {
    global $wpdb;

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

    if ( empty( $key ) )
        return new WP_Error('invalid_key', __('Invalid key'));

    $user = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->users WHERE
user_activation_key = %s", $key));
    if ( empty( $user ) )
        return new WP_Error('invalid_key', __('Invalid key'));
...[snip]....
line 276:
$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : 'login';
$errors = new WP_Error();

if ( isset($_GET['key']) )
    $action = 'resetpass';

// validate action so as to default to the login screen
if ( !in_array($action, array('logout', 'lostpassword', 'retrievepassword',
'resetpass', 'rp', 'register', 'login')) && false ===
has_filter('login_form_' . $action) )
    $action = 'login';
...[snip]....

line 370:

break;

case 'resetpass' :
case 'rp' :
    $errors = reset_password($_GET['key']);

    if ( ! is_wp_error($errors) ) {
        wp_redirect('wp-login.php?checkemail=newpass');
        exit();
    }

    wp_redirect('wp-login.php?action=lostpassword&error=invalidkey');
    exit();

break;
...[snip ]...
like image 946
PaulG Avatar asked Aug 12 '09 18:08

PaulG


1 Answers

So $key is an array in the querystring with a single empty string ['']

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

reset_password gets called with an array, and then preg_replace gets called:

 //$key = ['']
 $key = preg_replace('/[^a-z0-9]/i', '', $key);
 //$key = [''] still

because preg_replace accepts either a string or an array of strings. It regex replaces nothing and returns the same array. $key is not empty (it's an array of an empty string) so this happens:

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

Now from here, I need to go read the wordpress source for how prepare behaves...

More:

So prepare calls vsprintf which produces an empty string

$a = array('');
$b = array($a);
vsprintf("%s", $b);
//Does not produce anything

So the SQL is:

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

Which will apparently match the admin user (and all users without activation_keys I suppose).

And that's how.

like image 188
Tom Ritter Avatar answered Oct 21 '22 10:10

Tom Ritter