Make WordPress Core

Changeset 59893


Ignore:
Timestamp:
02/28/2025 06:51:44 PM (18 months ago)
Author:
johnbillion
Message:

Security: Reintroduce support for passwords hashed with MD5.

This reinstates the ability for a user to log in to an account where the password is hashed using MD5. This means that the ability to reset a password directly in the database using an SQL query or a database administration tool will be retained without the need to implement or integrate with bcrypt or phpass.

A password hashed with MD5 will get upgraded to bcrypt at the point where a user successfully logs in, just as is the case with a phpass hash.

Props audrasjb, aaronjorbin, johnbillion, david-innes, benniledl.

See #21022.

Location:
trunk
Files:
2 edited

Legend:

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

    r59858 r59893  
    27252725         * @since 6.8.0 Passwords in WordPress are now hashed with bcrypt by default. A
    27262726         *              password that wasn't hashed with bcrypt will be checked with phpass.
    2727          *              Passwords hashed with md5 are no longer supported.
    27282727         *
    27292728         * @global PasswordHash $wp_hasher phpass object. Used as a fallback for verifying
     
    27432742                global $wp_hasher;
    27442743
    2745                 $check = false;
    2746 
    2747                 // If the hash is still md5 or otherwise truncated then invalidate it.
    27482744                if ( strlen( $hash ) <= 32 ) {
    2749                         /**
    2750                          * Filters whether the plaintext password matches the hashed password.
    2751                          *
    2752                          * @since 2.5.0
    2753                          * @since 6.8.0 Passwords are now hashed with bcrypt by default.
    2754                          *              Old passwords may still be hashed with phpass.
    2755                          *
    2756                          * @param bool       $check    Whether the passwords match.
    2757                          * @param string     $password The plaintext password.
    2758                          * @param string     $hash     The hashed password.
    2759                          * @param string|int $user_id  Optional ID of a user associated with the password.
    2760                          *                             Can be empty.
    2761                          */
    2762                         return apply_filters( 'check_password', $check, $password, $hash, $user_id );
    2763                 }
    2764 
    2765                 if ( ! empty( $wp_hasher ) ) {
     2745                        // Check the hash using md5 regardless of the current hashing mechanism.
     2746                        $check = hash_equals( $hash, md5( $password ) );
     2747                } elseif ( ! empty( $wp_hasher ) ) {
    27662748                        // Check the password using the overridden hasher.
    27672749                        $check = $wp_hasher->CheckPassword( $password, $hash );
    27682750                } elseif ( strlen( $password ) > 4096 ) {
     2751                        // Passwords longer than 4096 characters are not supported.
    27692752                        $check = false;
    27702753                } elseif ( str_starts_with( $hash, '$wp' ) ) {
     
    27812764                }
    27822765
    2783                 /** This filter is documented in wp-includes/pluggable.php */
     2766                /**
     2767                 * Filters whether the plaintext password matches the hashed password.
     2768                 *
     2769                 * @since 2.5.0
     2770                 * @since 6.8.0 Passwords are now hashed with bcrypt by default.
     2771                 *              Old passwords may still be hashed with phpass or md5.
     2772                 *
     2773                 * @param bool       $check    Whether the passwords match.
     2774                 * @param string     $password The plaintext password.
     2775                 * @param string     $hash     The hashed password.
     2776                 * @param string|int $user_id  Optional ID of a user associated with the password.
     2777                 *                             Can be empty.
     2778                 */
    27842779                return apply_filters( 'check_password', $check, $password, $hash, $user_id );
    27852780        }
  • trunk/tests/phpunit/tests/auth.php

    r59828 r59893  
    319319         * @ticket 21022
    320320         */
    321         public function test_wp_check_password_does_not_support_md5_hashes() {
     321        public function test_wp_check_password_supports_md5_hash() {
    322322                $password = 'password';
    323323                $hash     = md5( $password );
    324                 $this->assertFalse( wp_check_password( $password, $hash ) );
     324                $this->assertTrue( wp_check_password( $password, $hash ) );
    325325                $this->assertSame( 1, did_filter( 'check_password' ) );
    326326        }
     
    364364        public function data_empty_values() {
    365365                return array(
    366                         // Integer zero:
    367                         array( 0 ),
    368366                        // String zero:
    369367                        array( '0' ),
     
    10851083         * @ticket 21022
    10861084         */
     1085        public function test_md5_password_is_rehashed_after_successful_user_password_authentication( $username_or_email ) {
     1086                $password = 'password';
     1087
     1088                // Set the user password with the old md5 algorithm.
     1089                self::set_user_password_with_md5( $password, self::$user_id );
     1090
     1091                // Verify that the password needs rehashing.
     1092                $hash = get_userdata( self::$user_id )->user_pass;
     1093                $this->assertTrue( wp_password_needs_rehash( $hash, self::$user_id ) );
     1094
     1095                // Authenticate.
     1096                $user = wp_authenticate( $username_or_email, $password );
     1097
     1098                // Verify that the md5 password hash was valid.
     1099                $this->assertNotWPError( $user );
     1100                $this->assertInstanceOf( 'WP_User', $user );
     1101                $this->assertSame( self::$user_id, $user->ID );
     1102
     1103                // Verify that the password no longer needs rehashing.
     1104                $hash = get_userdata( self::$user_id )->user_pass;
     1105                $this->assertFalse( wp_password_needs_rehash( $hash, self::$user_id ) );
     1106
     1107                // Authenticate a second time to ensure the new hash is valid.
     1108                $user = wp_authenticate( $username_or_email, $password );
     1109
     1110                // Verify that the bcrypt password hash is valid.
     1111                $this->assertNotWPError( $user );
     1112                $this->assertInstanceOf( 'WP_User', $user );
     1113                $this->assertSame( self::$user_id, $user->ID );
     1114        }
     1115
     1116        /**
     1117         * @dataProvider data_usernames
     1118         *
     1119         * @ticket 21022
     1120         */
    10871121        public function test_bcrypt_password_is_rehashed_with_new_cost_after_successful_user_password_authentication( $username_or_email ) {
    10881122                $password = 'password';
     
    17731807        }
    17741808
     1809        /**
     1810         * Test the tests
     1811         *
     1812         * @covers Tests_Auth::set_user_password_with_md5
     1813         *
     1814         * @ticket 21022
     1815         */
     1816        public function test_set_user_password_with_md5() {
     1817                $password = 'password';
     1818
     1819                // Set the user password with the old md5 algorithm.
     1820                self::set_user_password_with_md5( $password, self::$user_id );
     1821
     1822                // Ensure the password is hashed with md5.
     1823                $hash = get_userdata( self::$user_id )->user_pass;
     1824                $this->assertSame( md5( $password ), $hash );
     1825        }
     1826
     1827        private static function set_user_password_with_md5( string $password, int $user_id ) {
     1828                global $wpdb;
     1829
     1830                $wpdb->update(
     1831                        $wpdb->users,
     1832                        array(
     1833                                'user_pass' => md5( $password ),
     1834                        ),
     1835                        array(
     1836                                'ID' => $user_id,
     1837                        )
     1838                );
     1839                clean_user_cache( $user_id );
     1840        }
    17751841
    17761842        /**
Note: See TracChangeset for help on using the changeset viewer.