Make WordPress Core

Changeset 60125


Ignore:
Timestamp:
04/03/2025 02:36:46 PM (6 weeks ago)
Author:
johnbillion
Message:

Application Passwords: Correct the fallback behaviour for application passwords that don't use a generic hash.

Application passwords that aren't hashed using BLAKE2b should be checked using wp_check_password() rather than assuming they were hashed with phpass. This provides full back compat support for application passwords that were created via an overridden wp_hash_password() function that uses an alternative hashing algorithm.

Reviewed by audrasjb.
Merges [60123] into the 6.8 branch.

Props snicco, debarghyabanerjee, peterwilsoncc, jorbin, johnbillion.

Fixes #63203

Location:
branches/6.8
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • branches/6.8

  • branches/6.8/src/wp-includes/class-wp-application-passwords.php

    r59828 r60125  
    503503        string $hash
    504504    ): bool {
     505        if ( ! str_starts_with( $hash, '$generic$' ) ) {
     506            /*
     507             * If the hash doesn't start with `$generic$`, it is a hash created with `wp_hash_password()`.
     508             * This is the case for application passwords created before 6.8.0.
     509             */
     510            return wp_check_password( $password, $hash );
     511        }
     512
    505513        return wp_verify_fast_hash( $password, $hash );
    506514    }
  • branches/6.8/src/wp-includes/functions.php

    r60085 r60125  
    91519151 *
    91529152 * The function uses Sodium to hash the message and compare it to the hashed value. If the hash is not a generic hash,
    9153  * the hash is treated as a phpass portable hash in order to provide backward compatibility for application passwords
    9154  * which were hashed using phpass prior to WordPress 6.8.0.
     9153 * the hash is treated as a phpass portable hash in order to provide backward compatibility for passwords and security
     9154 * keys which were hashed using phpass prior to WordPress 6.8.0.
    91559155 *
    91569156 * @since 6.8.0
  • branches/6.8/tests/phpunit/tests/auth.php

    r59904 r60125  
    11361136
    11371137    /**
     1138     * @ticket 21022
     1139     * @ticket 63203
     1140     */
     1141    public function test_plain_bcrypt_application_password_is_accepted() {
     1142        add_filter( 'application_password_is_api_request', '__return_true' );
     1143        add_filter( 'wp_is_application_passwords_available', '__return_true' );
     1144
     1145        $password = 'password';
     1146
     1147        // Set an application password with plain bcrypt, which mimics a password that was hashed with
     1148        // a custom `wp_hash_password()` in use.
     1149        $uuid = self::set_application_password_with_plain_bcrypt( $password, self::$user_id );
     1150
     1151        // Authenticate.
     1152        $user = wp_authenticate_application_password( null, self::USER_LOGIN, $password );
     1153
     1154        // Verify that the plain bcrypt hash for the application password was valid.
     1155        $this->assertNotWPError( $user );
     1156        $this->assertInstanceOf( 'WP_User', $user );
     1157        $this->assertSame( self::$user_id, $user->ID );
     1158    }
     1159
     1160    /**
    11381161     * @dataProvider data_usernames
    11391162     *
     
    15901613        );
    15911614        $this->assertSame( $item['uuid'], rest_get_authenticated_app_password() );
     1615    }
     1616
     1617    /**
     1618     * @ticket 21022
     1619     * @ticket 63203
     1620     *
     1621     * @covers WP_Application_Passwords::create_new_application_password
     1622     */
     1623    public function test_application_password_is_hashed_with_fast_hash() {
     1624        // Create a new app-only password.
     1625        list( , $item ) = WP_Application_Passwords::create_new_application_password( self::$user_id, array( 'name' => 'phpunit' ) );
     1626
     1627        $this->assertStringStartsWith( '$generic$', $item['password'] );
    15921628    }
    15931629
     
    19672003     * Test the tests
    19682004     *
     2005     * @covers Tests_Auth::set_application_password_with_plain_bcrypt
     2006     *
     2007     * @ticket 21022
     2008     * @ticket 63203
     2009     */
     2010    public function test_set_application_password_with_plain_bcrypt() {
     2011        // Set an application password with the plain_bcrypt algorithm.
     2012        $uuid = self::set_application_password_with_plain_bcrypt( 'password', self::$user_id );
     2013
     2014        // Ensure the password is hashed with plain_bcrypt.
     2015        $hash = WP_Application_Passwords::get_user_application_password( self::$user_id, $uuid )['password'];
     2016        $this->assertStringStartsWith( '$2y$', $hash );
     2017    }
     2018
     2019    /**
     2020     * Creates an application password that is hashed using bcrypt instead of the generic algorithm.
     2021     *
     2022     * This is ultimately used to mimic a plugged version of `wp_hash_password()` that uses bcrypt and
     2023     * facilitate backwards compatibility testing.
     2024     *
     2025     * @param string $password The password to hash.
     2026     * @param int    $user_id  The user ID to associate the password with.
     2027     * @return string The UUID of the application password.
     2028     */
     2029    private static function set_application_password_with_plain_bcrypt( string $password, int $user_id ) {
     2030        return self::set_application_password( password_hash( $password, PASSWORD_BCRYPT ), $user_id );
     2031    }
     2032
     2033    /**
     2034     * Test the tests
     2035     *
    19692036     * @covers Tests_Auth::set_application_password_with_phpass
    19702037     *
     
    19802047    }
    19812048
     2049    /**
     2050     * Creates an application password that is hashed using a phpass portable hash instead of the generic algorithm.
     2051     *
     2052     * This facilitate backwards compatibility testing.
     2053     *
     2054     * @param string $password The password to hash.
     2055     * @param int    $user_id  The user ID to associate the password with.
     2056     * @return string The UUID of the application password.
     2057     */
    19822058    private static function set_application_password_with_phpass( string $password, int $user_id ) {
     2059        return self::set_application_password( self::$wp_hasher->HashPassword( $password ), $user_id );
     2060    }
     2061
     2062    /**
     2063     * Creates an application password using the given password hash.
     2064     *
     2065     * @param string $hash    The password hash.
     2066     * @param int    $user_id The user ID to associate the password with.
     2067     * @return string The UUID of the application password.
     2068     */
     2069    private static function set_application_password( string $hash, int $user_id ) {
    19832070        $uuid = wp_generate_uuid4();
    19842071        $item = array(
     
    19862073            'app_id'    => '',
    19872074            'name'      => 'Test',
    1988             'password'  => self::$wp_hasher->HashPassword( $password ),
     2075            'password'  => $hash,
    19892076            'created'   => time(),
    19902077            'last_used' => null,
Note: See TracChangeset for help on using the changeset viewer.