User password field is empty

I still wouldn’t recommend this because it is probably exploitable somehow…

Technically assuming all the right hooks and functions are called, a user should not be able to login if supplying a blank password even if the user_pass field in the database is empty.

Why…?

When you try to login using the form as seen on /wp-login./php, WordPress will call wp_authenticate_username_password() which is hooked onto the authenticate action.

wp_authenticate_username_password() checks to see if the submitted $password is not empty. If it is empty, it will raise an instance of WP_Error and the login request will be rejected.

The same applies anywhere you try to login from, be it a custom login form or not, assuming the correct hooks or functions are called, e.g. authenticate or wp_authenticate_username_password().

Now even if you make it to the wp_check_password() point of wp_authenticate_username_password(), an empty password still generates a hash value when ran through md5(), e.g. md5('') //d41d8cd98f00b204e9800998ecf8427e.

So what happens when we enter the wp_check_password() function is that the following logic is ran…

Source: wp-includes/pluggable.php

function wp_check_password($password, $hash, $user_id = '') {
    global $wp_hasher;

//note that...
//$password  = '' and $hash (from db) = ''

// If the hash is still md5...
if ( strlen($hash) <= 32 ) {
    $check = hash_equals( $hash, md5( $password ) ); // <- $check will return false, because the $hash is empty and does not match the hash of md5($password)

    if ( $check && $user_id ) {
        // Rehash using new hash.
        wp_set_password($password, $user_id);
        $hash = wp_hash_password($password);
    }

    /**
     * Filter whether the plaintext password matches the encrypted password.
     *
     * @since 2.5.0
     *
     * @param bool   $check    Whether the passwords match.
     * @param string $password The plaintext password.
     * @param string $hash     The hashed password.
     * @param int    $user_id  User ID.
     */
    return apply_filters( 'check_password', $check, $password, $hash, $user_id );
}


// If the stored hash is longer than an MD5, presume the
// new style phpass portable hash.
if ( empty($wp_hasher) ) {
    require_once( ABSPATH . WPINC . '/class-phpass.php');
    // By default, use the portable hash from phpass
    $wp_hasher = new PasswordHash(8, true);
}

$check = $wp_hasher->CheckPassword($password, $hash);

/** This filter is documented in wp-includes/pluggable.php */
return apply_filters( 'check_password', $check, $password, $hash, $user_id );

}

Ok so once we hit:

if ( strlen($hash) <= 32 ) {
    $check = hash_equals( $hash, md5( $password ) );

$check will return false because the value of $hash is an empty string and the value of md5($password) which is essentially md5('') is d41d8cd98f00b204e9800998ecf8427e as I mentioned above.

Therefore login attempt will be rejected.

So in theory you are safe unless some malicious code hooks onto:

return apply_filters( 'check_password', $check, $password, $hash, $user_id );

…and returns true. But anyone could do that under normal circumstances where proper password hashes do exist in the database.

Recommendation:

Leaving user_pass field in database empty and void of hashes is sloppy and bad practice.

Assumming someone knew that all the passwords in the database were blank and wanted to screw you over, they’d be half-way there in doing so. All they need to do is find a way to exploit the system.

Seeing as it is unlikely that you have audited every piece of internal core code as well as code from plugins and likely your theme, you don’t know when or if blank passwords will come back to bite you in the ass because of some edge-case zero day exploit that is introduced.

Instead…

//you can use get_users() if you prefer instead of $wpdb directly...
//this is just a one time operation.. don't forget to exclude those
//users whom you do not wish to reset a password for, e.g. yourself

global $wpdb;

$users = $wpdb->get_col("SELECT ID FROM {$wpdb->prefix}users WHERE 1 = 1");

foreach ($users as $user_id) {
    wp_set_password(wp_generate_password(), $user_id);
}

…do yourself a favor and set passwords for all users and let the user reset the password or generate an email with a password reset link.