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 ]...
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With