The Exploit
An unauthenticated attacker with no special role needs only to craft a URL. When a victim or the attacker themselves visits the malicious link, WordPress logs in the attacker as the first administrator who previously used Facebook or Google social login—without a password or valid token.
GET /?jupiterx-facebook-social-login=123456789 HTTP/1.1
Host: target-wordpress.local
The response is a 302 redirect. The attacker's browser follows it and lands on the WordPress dashboard. A wordpress_logged_in_* cookie has been set. The attacker is now authenticated as the target user—typically an administrator—and can create accounts, modify posts, inject malicious plugins, or exfiltrate data.
The core issue: the plugin stored a Facebook or Google user ID in WordPress user metadata (social-media-user-facebook-id, social-media-user-google-id), then accepted that same ID directly from a URL query parameter ($_GET['jupiterx-facebook-social-login'] or $_GET['jupiterx-google-social-login']) and logged in whichever user matched it—with zero server-side validation that the social login flow had actually completed.
What the Patch Did
Before
public function facebook_log_user_in() {
if ( empty( $_GET['jupiterx-facebook-social-login'] ) ) {
return;
}
$user_facebook_id = sanitize_text_field( $_GET['jupiterx-facebook-social-login'] );
$user_query = new \WP_User_Query( [
'meta_key' => 'social-media-user-facebook-id',
'meta_value' => $user_facebook_id,
'number' => 1,
'count_total' => false,
] );
$users = $user_query->get_results();
if ( empty( $users ) ) {
wp_die( esc_html__( 'No user found.', 'jupiterx-core' ) );
}
$user_id = $users[0]->ID;
wp_clear_auth_cookie();
wp_set_current_user( $user_id );
wp_set_auth_cookie( $user_id );
}
After
// Hook removed entirely. Authentication now in AJAX handler only.
// No init hook registration. No $_GET processing.
// Within the AJAX handler, after validating the token server-side:
if ( ! empty( $user_id ) ) {
wp_clear_auth_cookie();
wp_set_current_user( $user_id );
wp_set_auth_cookie( $user_id );
}
$login = [
'siteURL' => site_url(),
];
if ( ! empty( $ajax_handler->form['settings']['redirect_url']['url'] ) ) {
$login['redirectUrl'] = $ajax_handler->form['settings']['redirect_url']['url'];
}
The patch removed the add_action( 'init', [ $this, 'facebook_log_user_in' ] ) hook registration entirely, eliminating the public-facing entry point that accepted untrusted $_GET parameters. Authentication now occurs only within the AJAX handler, which validates the social login token directly against Facebook or Google's servers before calling wp_set_auth_cookie(). The redirect URL is no longer read from user input; it is sourced from the form settings, controlled server-side.
Root Cause
CWE-287: Improper Authentication and CWE-639: Authorization in a Different Sphere of Control.
The attacker-controlled value—the Facebook or Google ID in $_GET['jupiterx-facebook-social-login']—enters the request as a URL parameter. It flows directly into a WP_User_Query lookup without any validation that the user actually completed a social login handshake with the external provider. The plugin trusted that an ID in the URL was proof of authentication, when in fact the URL was merely a public parameter that any attacker could forge. The trust boundary between the unauthenticated user and the social login provider was crossed without a cryptographic token or server-side state check.
Why It Works
The load-bearing fix is the removal of the init hook that registered facebook_log_user_in() as a callback. If that hook were still in place and still read $_GET['jupiterx-facebook-social-login'], the vulnerability would remain exploitable—the attacker would still be able to forge the parameter and log in.
The additional lines—moving to AJAX, validating the token server-side, returning redirect information in JSON rather than via wp_redirect()—form a defence-in-depth strategy. The AJAX context allows the handler to verify that the token came from a legitimate callback request (with proper nonce and form context). Server-side token validation ensures the social login provider actually signed off on the authentication. And returning the redirect in JSON rather than accepting it from a parameter closes the secondary CWE-601 open redirect vulnerability in the same function. Any one of these missing would leave the bug partially exploitable.
Hardening Checklist
- Never derive authentication state from user-supplied URL parameters alone. Use
filter_input( INPUT_POST, 'token', FILTER_SANITIZE_SPECIAL_CHARS )for OAuth tokens and validate them against the external provider before calling anywp_set_current_user()orwp_set_auth_cookie(). - Require nonce verification on authentication actions. Call
check_ajax_referer()orwp_verify_nonce()on any AJAX endpoint or form handler that modifies authentication state, even if it is public-facing. - Store and validate server-side state for multi-step flows. For OAuth handshakes, generate a random state token, store it in the session or user meta with an expiration, and verify it matches the callback parameter before logging in.
- Do not query users by untrusted metadata. If you must use
WP_User_Querywithmeta_keyandmeta_value, ensure the value has been validated or cryptographically signed by a trusted source—never directly from a URL parameter. - Move redirect URLs to form/option settings, not user input. Use
wp_safe_remote_post()to validate redirect targets against a whitelist, or store the redirect in post meta or plugin options and retrieve it server-side—never accept it from$_GETor$_POST.
References
- https://nvd.nist.gov/vuln/detail/CVE-2024-7781