The Exploit
An unauthenticated attacker with control over a social identity provider (or ability to register an account on one) can log in as any existing WordPress user, including administrators, by submitting a social login callback with an unverified email address matching a target account.
GET /wp-login.php?loginizer_social_login=google&code=ATTACKER_CONTROLLED_AUTH_CODE HTTP/1.1
Host: target-wordpress.local
User-Agent: Mozilla/5.0
The attacker crafts or intercepts a social login callback where the code parameter is issued by a provider (Google, Facebook, etc.) for an account with email [email protected] but where emailVerified is false in the provider's user profile object. When the callback is processed by social-login.php, the plugin retrieves the user profile from HybridAuth and matches the email address to the existing WordPress admin account without verifying that the provider has actually confirmed ownership of that email. The attacker receives a valid WordPress session cookie and full access to the administrator account.
What the Patch Did
Before
$adapter = $hybridauth->getAdapter($provider);
$userProfile = $adapter->getUserProfile();
$accessToken = $adapter->getAccessToken();
$data = [
'access_token' => $accessToken,
After
$adapter = $hybridauth->getAdapter($provider);
$userProfile = $adapter->getUserProfile();
$accessToken = $adapter->getAccessToken();
// Check if the user have account which is verified
if(empty($userProfile->emailVerified)){
self::$error['login_failed'] = __('The social account you are using does not have a verified email.', 'loginizer');
$adapter->disconnect();
self::trigger_error();
return;
}
$data = [
'access_token' => $accessToken,
The patch introduces an explicit email verification check using the emailVerified property returned by the social provider's user profile object. Before processing the login, the code now validates that $userProfile->emailVerified is not empty. If the check fails, the plugin disconnects the adapter and triggers an error, preventing authentication. This is a positive security control — a blocklist that rejects unverified identities rather than assuming all profile data from a provider is trustworthy by default.
Root Cause
CWE-640: Weak Verification of Cryptographic Signature (and CWE-287: Improper Authentication).
The vulnerability stems from insufficient trust boundary validation in the social login callback handler. When a user initiates social login, HybridAuth returns a user profile object containing email and verification status. The plugin's original code trusted that any email address in $userProfile->email belonged to the authenticated user without independently validating the emailVerified flag. An attacker who controls a social account (or registers one) can supply any email address during the provider's OAuth flow. Providers like Google accept unverified email claims in certain contexts or allow accounts with unverified secondaryemails. The plugin's dataflow — extracting email from the provider response, querying wp_users by that email, and issuing a session without confirming email ownership — crossed the trust boundary at the provider without a verification gate. The attacker-controlled value is the email address in the provider callback; the sink is the WordPress user lookup and session creation; the unchecked boundary is the provider's claim of email ownership.
Why It Works
The load-bearing line is the conditional: if(empty($userProfile->emailVerified)). Removing this check alone would restore the vulnerability — the plugin would continue accepting unverified emails and matching them to WordPress accounts. The additional lines ($adapter->disconnect(), self::trigger_error(), and return) are defence-in-depth housekeeping: they ensure the adapter is cleaned up, an error is logged for audit visibility, and the function exits immediately to prevent any further processing. However, without the conditional itself, no amount of cleanup prevents the attack. The engineer added the extra statements because security controls should fail safely and log intent — a bare return leaves ambiguity about whether an error occurred, whereas an explicit disconnect and error trigger signal to the wider application and to administrators (via logs) that authentication was refused due to unverified email, not a network timeout or malformed response.
Hardening Checklist
-
Validate third-party claims against provider-specific trust signals: Always check provider-issued verification flags (like
emailVerified,email_verified, or equivalent) before trusting identity claims. Use HybridAuth's profile metadata fields rather than assuming all populated fields are verified. -
Implement email verification for social-linked accounts: Before linking a social provider account to an existing WordPress user, send a verification email to that address or require the user to confirm the link in their WordPress dashboard. Do not auto-link based solely on email match.
-
Log authentication failures with sufficient context: Call
wp_insert_activity_log()(if available via a logging plugin) or write toerror_log()with the provider, email, and reason for rejection. This surfaces patterns of failed social login attempts that indicate probe attacks. -
Add a rate limit or CAPTCHA to the social login callback handler: Use a transient or a third-party service like Cloudflare to throttle repeated failed social login attempts from the same IP or email. This slows account enumeration and takeover attempts.
-
Enforce email domain whitelisting for administrative users (if applicable): If the WordPress site has a small set of allowed administrator email domains (e.g.,
@company.com), add a check in the social login handler to reject social accounts with email addresses outside that set before creating a session.
References
- https://nvd.nist.gov/vuln/detail/CVE-2024-10097