'administrator']); // On a multisite network, super-admins hold full control too — fold them // into the admin set (deduped by ID) so a super-admin without 2FA or with // a weak password is scanned like any other administrator. if (function_exists('is_multisite') && is_multisite() && function_exists('get_super_admins')) { $byId = []; foreach ($admins as $a) { $byId[$a->ID] = $a; } foreach (get_super_admins() as $login) { $su = get_user_by('login', $login); if ($su instanceof \WP_User) { $byId[$su->ID] = $su; } } $admins = array_values($byId); } $disposable = ['mailinator.com', 'guerrillamail.com', 'tempmail.com', '10minutemail.com', 'yopmail.com', 'trashmail.com', 'sharklasers.com', 'throwaway.email', 'dispostable.com']; $disposableAdmins = []; foreach ($admins as $admin) { $email = $admin->user_email ?? ''; $atPart = is_string($email) ? strrchr($email, '@') : false; $domain = $atPart === false ? '' : strtolower((string) substr($atPart, 1)); if ($domain !== '' && in_array($domain, $disposable, true)) { $disposableAdmins[] = $email; } } $hasAdminUser = false; $adminsWithout2fa = 0; $adminsWithWeakPassword = 0; foreach ($admins as $user) { if ($user->user_login === 'admin') { $hasAdminUser = true; } if (!$this->has2fa($user->ID)) { $adminsWithout2fa++; } if ($this->hasWeakPassword($user)) { $adminsWithWeakPassword++; } } return AdminUsersScorer::score( $hasAdminUser, count($admins), $adminsWithout2fa, $adminsWithWeakPassword, $disposableAdmins ); } catch (\Throwable $e) { return new CheckResult( 'admin_users', 'skip', 10, 10, 'info', ['error' => $e->getMessage()], 'Vérification ignorée (exception interne) : ' . $e->getMessage() ); } } /** * Real weak-password detection: test a small dictionary of the most common * passwords (plus username-derived candidates) against the user's stored hash * with wp_check_password. A match is a definitive weak password — zero false * positives, unlike the previous "last changed" heuristic which flagged everyone. */ private function hasWeakPassword(\WP_User $user): bool { if (!function_exists('wp_check_password') || empty($user->user_pass)) { return false; } $common = [ 'password', 'password1', 'password123', 'password!', 'passw0rd', '123456', '12345678', '123456789', '1234567890', '12345', '1234', 'qwerty', 'qwerty123', 'azerty', 'azerty123', 'admin', 'admin123', 'administrator', 'root', 'toor', 'letmein', 'welcome', 'welcome1', 'welcome123', 'iloveyou', 'changeme', 'motdepasse', 'secret', 'abc123', 'abcd1234', '111111', '000000', '123123', '654321', 'monkey', 'dragon', 'football', 'baseball', 'master', 'shadow', 'superman', 'sunshine', 'princess', 'qwertyuiop', '1q2w3e4r', '1qaz2wsx', 'trustno1', 'whatever', 'login', 'guest', 'test', 'test123', 'wordpress', 'wp-admin', ]; $login = (string) $user->user_login; $candidates = array_merge($common, [$login, strtolower($login), $login . '123', $login . '1']); foreach (array_unique(array_filter($candidates)) as $candidate) { if (wp_check_password($candidate, $user->user_pass, $user->ID)) { return true; } } return false; } private function has2fa(int $userId): bool { $metaKeys = [ 'two_fa_secret', '_two_factor_enabled_providers', // core "Two Factor" plugin '_two_factor_provider', 'wordfence_2fa_enabled', '_wf2fa_secret', 'itsec_two_factor_enabled', 'duo_user_id', // Duo Security 'googleauthenticator_enabled', // Google Authenticator 'ga_enabled', 'miniorange_2fa_enabled', // miniOrange 2FA 'wp_2fa_totp_key', // WP 2FA ]; foreach ($metaKeys as $key) { if (!empty(get_user_meta($userId, $key, true))) { return true; } } return false; } }