From b5e0c16c04edcebdf5e81c39796a9d2c22cfd6f7 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Mon, 3 Mar 2025 09:49:36 +0000 Subject: [PATCH] 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 git-svn-id: https://develop.svn.wordpress.org/trunk@59904 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/functions.php | 3 +- tests/phpunit/tests/auth.php | 93 +++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 9a6938ed64..5696852f52 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -9142,7 +9142,8 @@ function wp_fast_hash( #[\SensitiveParameter] string $message ): string { - return '$generic$' . sodium_bin2hex( sodium_crypto_generichash( $message ) ); + $hashed = sodium_crypto_generichash( $message, 'wp_fast_hash_6.8+', 30 ); + return '$generic$' . sodium_bin2base64( $hashed, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING ); } /** diff --git a/tests/phpunit/tests/auth.php b/tests/phpunit/tests/auth.php index 6c22b89855..b8b6197a20 100644 --- a/tests/phpunit/tests/auth.php +++ b/tests/phpunit/tests/auth.php @@ -473,6 +473,99 @@ class Tests_Auth extends WP_UnitTestCase { $this->assertSame( self::$user_id, $user->ID ); } + public function data_passwords(): array { + return array( + array( 'a' ), + array( 'password' ), + array( str_repeat( 'a', self::$password_length_limit ) ), + ); + } + + /** + * Ensure the hash of the user password remains less than 64 characters in length to account for the old users table schema. + * + * @ticket 21022 + * @dataProvider data_passwords + */ + public function test_user_password_against_old_users_table_schema( string $password ) { + // Mimic the schema of the users table prior to WordPress 4.4. + add_filter( 'wp_pre_insert_user_data', array( $this, 'mimic_users_schema_prior_to_44' ) ); + + $username = 'old-schema-user'; + + // Create a user. + $user_id = $this->factory()->user->create( + array( + 'user_login' => $username, + 'user_email' => 'old-schema-user@example.com', + 'user_pass' => $password, + ) + ); + + // Check the user can authenticate. + $user = wp_authenticate( $username, $password ); + + $this->assertNotWPError( $user ); + $this->assertInstanceOf( 'WP_User', $user ); + $this->assertSame( $user_id, $user->ID, 'User should be able to authenticate' ); + $this->assertNotSame( self::$user_id, $user->ID, 'A unique user must be created for this test, the shared fixture must not be used' ); + } + + /** + * Ensure the hash of the user activation key remains less than 60 characters in length to account for the old users table schema. + * + * @ticket 21022 + */ + public function test_user_activation_key_against_old_users_table_schema() { + // Mimic the schema of the users table prior to WordPress 4.4. + add_filter( 'wp_pre_insert_user_data', array( $this, 'mimic_users_schema_prior_to_44' ) ); + + $username = 'old-schema-user'; + + // Create a user. + $user_id = $this->factory()->user->create( + array( + 'user_login' => $username, + 'user_email' => 'old-schema-user@example.com', + ) + ); + + $user = get_userdata( $user_id ); + $key = get_password_reset_key( $user ); + + // A correctly saved key should be accepted. + $check = check_password_reset_key( $key, $user->user_login ); + + $this->assertNotWPError( $check ); + $this->assertInstanceOf( 'WP_User', $check ); + $this->assertSame( $user->ID, $check->ID ); + $this->assertNotSame( self::$user_id, $user->ID, 'A unique user must be created for this test, the shared fixture must not be used' ); + } + + /* + * Fake the schema of the users table prior to WordPress 4.4 to mimic sites that are using the + * `DO_NOT_UPGRADE_GLOBAL_TABLES` constant and have not updated the users table schema. + * + * The schema of the wp_users table on wordpress.org has not been updated since the schema was changed in [35638] + * for WordPress 4.4, which means the `user_activation_key` field remains at 60 characters length and the `user_pass` + * field remains at 64 characters length instead of the expected 255. Although this is unlikely to affect other + * sites, this can be accommodated for in the codebase. + * + * Actually altering the database schema during tests will commit the transaction and break subsequent tests, hence + * the use of this filter. + */ + public function mimic_users_schema_prior_to_44( array $data ): array { + if ( isset( $data['user_pass'] ) ) { + $this->assertLessThanOrEqual( 64, strlen( $data['user_pass'] ) ); + } + + if ( isset( $data['user_activation_key'] ) ) { + $this->assertLessThanOrEqual( 60, strlen( $data['user_activation_key'] ) ); + } + + return $data; + } + /** * @ticket 21022 */