Python Authentication with WordPress

Today, I was working on a script to automate detection of poor WordPress admin passwords by using the top 500 commonly used bad passwords. Digging into WordPress core functions is a simple task and I was quickly able to narrow down how they generate their password hashes to the following functions in wp-includes/pluggable.php:

function wp_hash_password($password) {
    global $wp_hasher;
    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);
    }
    return $wp_hasher->HashPassword( trim( $password ) );
}

This snippet of code uses phpass to securely store the password in the database. For more detail see: http://www.openwall.com/phpass/

Reviewing their documentation I saw they had a python port here:
https://github.com/exavolt/python-phpass

This python egg is what does all the real work and allows us to replicate the check function WordPress uses internally in Python.

PHP:

function wp_check_password($password, $hash, $user_id = '') {
    global $wp_hasher;
    // If the hash is still md5...
    if ( strlen($hash) <= 32 ) {
        $check = ( $hash == 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 $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 );
}

To Python:

'''
Test the site admin against common easily crackable passwords
'''
def test_passwords(data, password_list):
    url = data['url'][0]
    users = data['users']
    user_count = len(users)
    total_md5 = 0
    errors = []
    insecure = []
    for u in users:
        username = u[0]
        password_hash = u[1]
        if len(password_hash) <= 32:
            '''
            Haven't written support yet for older versions and md5 conversion
            '''
            total_md5 += 1
        else:
            wp_hasher = phpass.PasswordHash(8, True)
            for p in password_list:
                p = p.strip()
                check = wp_hasher.check_password(p, password_hash)
                if check:
                    insecure.append("[!] Insecure password found for admin user %s:%s on %s" % (username, p, url))
    if total_md5 == user_count:
        errors.append("[!] All admin users require conversion from MD5 on %s" % url)
    return errors, insecure

For more details see:
https://github.com/morissette/insecure-wp-admin-password-check

Write a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.