Make WordPress Core

Changeset 59904


Ignore:
Timestamp:
03/03/2025 09:49:36 AM (8 weeks ago)
Author:
johnbillion
Message:

Security: Reduce the length of the hash returned by wp_fast_hash() so it can be used in the user_activation_key field when a legacy database schema is still in use.

This reduces the hash length from 32 bytes to 30 so the overall length of an activation key after encoding, prefixing, and prepending a timestamp fits into 60 bytes.

A key is also introduced for domain separation. This doesn't affect the output length.

Props dd32, paragoninitiativeenterprises, peterwilsoncc, johnbillion

Fixes #21022

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/functions.php

    r59830 r59904  
    91439143    string $message
    91449144): string {
    9145     return '$generic$' . sodium_bin2hex( sodium_crypto_generichash( $message ) );
     9145    $hashed = sodium_crypto_generichash( $message, 'wp_fast_hash_6.8+', 30 );
     9146    return '$generic$' . sodium_bin2base64( $hashed, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING );
    91469147}
    91479148
  • trunk/tests/phpunit/tests/auth.php

    r59893 r59904  
    474474    }
    475475
     476    public function data_passwords(): array {
     477        return array(
     478            array( 'a' ),
     479            array( 'password' ),
     480            array( str_repeat( 'a', self::$password_length_limit ) ),
     481        );
     482    }
     483
     484    /**
     485     * Ensure the hash of the user password remains less than 64 characters in length to account for the old users table schema.
     486     *
     487     * @ticket 21022
     488     * @dataProvider data_passwords
     489     */
     490    public function test_user_password_against_old_users_table_schema( string $password ) {
     491        // Mimic the schema of the users table prior to WordPress 4.4.
     492        add_filter( 'wp_pre_insert_user_data', array( $this, 'mimic_users_schema_prior_to_44' ) );
     493
     494        $username = 'old-schema-user';
     495
     496        // Create a user.
     497        $user_id = $this->factory()->user->create(
     498            array(
     499                'user_login' => $username,
     500                'user_email' => 'old-schema-user@example.com',
     501                'user_pass'  => $password,
     502            )
     503        );
     504
     505        // Check the user can authenticate.
     506        $user = wp_authenticate( $username, $password );
     507
     508        $this->assertNotWPError( $user );
     509        $this->assertInstanceOf( 'WP_User', $user );
     510        $this->assertSame( $user_id, $user->ID, 'User should be able to authenticate' );
     511        $this->assertNotSame( self::$user_id, $user->ID, 'A unique user must be created for this test, the shared fixture must not be used' );
     512    }
     513
     514    /**
     515     * Ensure the hash of the user activation key remains less than 60 characters in length to account for the old users table schema.
     516     *
     517     * @ticket 21022
     518     */
     519    public function test_user_activation_key_against_old_users_table_schema() {
     520        // Mimic the schema of the users table prior to WordPress 4.4.
     521        add_filter( 'wp_pre_insert_user_data', array( $this, 'mimic_users_schema_prior_to_44' ) );
     522
     523        $username = 'old-schema-user';
     524
     525        // Create a user.
     526        $user_id = $this->factory()->user->create(
     527            array(
     528                'user_login' => $username,
     529                'user_email' => 'old-schema-user@example.com',
     530            )
     531        );
     532
     533        $user = get_userdata( $user_id );
     534        $key  = get_password_reset_key( $user );
     535
     536        // A correctly saved key should be accepted.
     537        $check = check_password_reset_key( $key, $user->user_login );
     538
     539        $this->assertNotWPError( $check );
     540        $this->assertInstanceOf( 'WP_User', $check );
     541        $this->assertSame( $user->ID, $check->ID );
     542        $this->assertNotSame( self::$user_id, $user->ID, 'A unique user must be created for this test, the shared fixture must not be used' );
     543    }
     544
     545    /*
     546     * Fake the schema of the users table prior to WordPress 4.4 to mimic sites that are using the
     547     * `DO_NOT_UPGRADE_GLOBAL_TABLES` constant and have not updated the users table schema.
     548     *
     549     * The schema of the wp_users table on wordpress.org has not been updated since the schema was changed in [35638]
     550     * for WordPress 4.4, which means the `user_activation_key` field remains at 60 characters length and the `user_pass`
     551     * field remains at 64 characters length instead of the expected 255. Although this is unlikely to affect other
     552     * sites, this can be accommodated for in the codebase.
     553     *
     554     * Actually altering the database schema during tests will commit the transaction and break subsequent tests, hence
     555     * the use of this filter.
     556     */
     557    public function mimic_users_schema_prior_to_44( array $data ): array {
     558        if ( isset( $data['user_pass'] ) ) {
     559            $this->assertLessThanOrEqual( 64, strlen( $data['user_pass'] ) );
     560        }
     561
     562        if ( isset( $data['user_activation_key'] ) ) {
     563            $this->assertLessThanOrEqual( 60, strlen( $data['user_activation_key'] ) );
     564        }
     565
     566        return $data;
     567    }
     568
    476569    /**
    477570     * @ticket 21022
Note: See TracChangeset for help on using the changeset viewer.