wordpress忘记密码重置密码方法以及相关登录源码解读

675 阅读1分钟

一、重置密码

安装最新版 wordpress-5.4.2-zh_CN.zip 很长时间未维护,登录时候突然发现密码忘记了,记录下解决办法吧。

  1. 重置登录密码为:123456
UPDATE `wp_users` SET user_pass = '$P$BiPHCyMrQlCHFzG/1ftoDdulGpfsoP0' WHERE ID =1
  1. 使用重置的登录后台设置安全密码

进入登录后台 >> 用户 >> 所有用户 选项卡 ;点击 编辑 按钮,找到 新密码 >> 生成密码 按钮,输入想设置的密码,最后 更新个人资料

二、登录源码解读

1. 登录控制器方法

  • 在 wp-login.php 1174 行 --- 查询数据库用户信息
// If the user wants SSL but the session is not SSL, force a secure cookie.
if ( ! empty( $_POST['log'] ) && ! force_ssl_admin() ) {
	$user_name = sanitize_user( wp_unslash( $_POST['log'] ) );
	$user      = get_user_by( 'login', $user_name ); // 这里查询数据库用户信息
    // ...
}
// ...
$user = wp_signon( array(), $secure_cookie ); // 这里校验密码
  • 其中 get_user_by() 方法最终执行类似下面的 SQL
$user = $wpdb->get_row(
	$wpdb->prepare(
		"SELECT * FROM $wpdb->users WHERE $db_field = %s LIMIT 1",
		$value
	)
);

2. 校验密码的逻辑

  • 接下来是校验密码的逻辑: 主要使用 wp_signon() 方法获取;代码在 wp-includes/user.php 文件中第 95 行 $user = wp_authenticate( $credentials['user_login'], $credentials['user_password'] ); 方法最终会调用 wp-includes/pluggable.php 文件的 function wp_check_password( $password, $hash, $user_id = '' ) {} 去检测密码。
function wp_signon( $credentials = array(), $secure_cookie = '' ) {
	// ...
	$user = wp_authenticate( $credentials['user_login'], $credentials['user_password'] );

	if ( is_wp_error( $user ) ) {
		return $user;
	}
	// ...
	return $user;
}

  • wp-includes/pluggable.php >> 2405 行 $check = $wp_hasher->CheckPassword( $password, $hash ); 方法,调用的是 wp-includes/class-phpass.php 文件的 CheckPassword()
function wp_check_password( $password, $hash, $user_id = '' ) {
	global $wp_hasher;
	// ...
	$check = $wp_hasher->CheckPassword( $password, $hash );
}

wp-includes/class-phpass.php 文件的 CheckPassword

CheckPassword() 方法又调用内部 $hash = $this->crypt_private($password, $stored_hash);

crypt_private 继续调用 $output .= $this->encode64($input, 6); 内部方法,最终返回的 $output = '$P$BlQ4L10EWkfoyGXC0EtVK.KdwW9WxW.'; 类似的字符串。

最终比较 return $hash === $stored_hash; 这两个字符串是否全等。

class PasswordHash {

    function CheckPassword($password, $stored_hash)
    {
    	if ( strlen( $password ) > 4096 ) {
    		return false;
    	}
    
    	$hash = $this->crypt_private($password, $stored_hash);
    	if ($hash[0] == '*')
    		$hash = crypt($password, $stored_hash);
    
    	return $hash === $stored_hash;
    }
    
    function crypt_private($password, $setting)
    {
    	$output = '*0';
    	if (substr($setting, 0, 2) == $output)
    		$output = '*1';
    
    	$id = substr($setting, 0, 3);
    	# We use "$P$", phpBB3 uses "$H$" for the same thing
    	if ($id != '$P$' && $id != '$H$')
    		return $output;
    
    	$count_log2 = strpos($this->itoa64, $setting[3]);
    	if ($count_log2 < 7 || $count_log2 > 30)
    		return $output;
    
    	$count = 1 << $count_log2;
    
    	$salt = substr($setting, 4, 8);
    	if (strlen($salt) != 8)
    		return $output;
    
    	# We're kind of forced to use MD5 here since it's the only
    	# cryptographic primitive available in all versions of PHP
    	# currently in use.  To implement our own low-level crypto
    	# in PHP would result in much worse performance and
    	# consequently in lower iteration counts and hashes that are
    	# quicker to crack (by non-PHP code).
    	if (PHP_VERSION >= '5') {
    		$hash = md5($salt . $password, TRUE);
    		do {
    			$hash = md5($hash . $password, TRUE);
    		} while (--$count);
    	} else {
    		$hash = pack('H*', md5($salt . $password));
    		do {
    			$hash = pack('H*', md5($hash . $password));
    		} while (--$count);
    	}
    
    	$output = substr($setting, 0, 12);
    	$output .= $this->encode64($hash, 16);
    
    	return $output;
    }
    
    function crypt_private($password, $setting)
    {
    	$output = '*0';
    	if (substr($setting, 0, 2) == $output)
    		$output = '*1';
    
    	$id = substr($setting, 0, 3);
    	# We use "$P$", phpBB3 uses "$H$" for the same thing
    	if ($id != '$P$' && $id != '$H$')
    		return $output;
    
    	$count_log2 = strpos($this->itoa64, $setting[3]);
    	if ($count_log2 < 7 || $count_log2 > 30)
    		return $output;
    
    	$count = 1 << $count_log2;
    
    	$salt = substr($setting, 4, 8);
    	if (strlen($salt) != 8)
    		return $output;
    
    	# We're kind of forced to use MD5 here since it's the only
    	# cryptographic primitive available in all versions of PHP
    	# currently in use.  To implement our own low-level crypto
    	# in PHP would result in much worse performance and
    	# consequently in lower iteration counts and hashes that are
    	# quicker to crack (by non-PHP code).
    	if (PHP_VERSION >= '5') {
    		$hash = md5($salt . $password, TRUE);
    		do {
    			$hash = md5($hash . $password, TRUE);
    		} while (--$count);
    	} else {
    		$hash = pack('H*', md5($salt . $password));
    		do {
    			$hash = pack('H*', md5($hash . $password));
    		} while (--$count);
    	}
    
    	$output = substr($setting, 0, 12);
    	$output .= $this->encode64($hash, 16);
    
    	return $output;
    }
    
    function encode64($input, $count)
	{
		$output = '';
		$i = 0;
		do {
			$value = ord($input[$i++]);
			$output .= $this->itoa64[$value & 0x3f];
			if ($i < $count)
				$value |= ord($input[$i]) << 8;
			$output .= $this->itoa64[($value >> 6) & 0x3f];
			if ($i++ >= $count)
				break;
			if ($i < $count)
				$value |= ord($input[$i]) << 16;
			$output .= $this->itoa64[($value >> 12) & 0x3f];
			if ($i++ >= $count)
				break;
			$output .= $this->itoa64[($value >> 18) & 0x3f];
		} while ($i < $count);

		return $output;
	}
}

3. 总结

总结: 最终加密的算法就是在 wp-includes/class-phpass.php 文件的 crypt_private 方法,其中 $count = 1 << $count_log2; 决定了 md5hash 值,有空可以读读上面的逻辑。