How XSS Turns Passkeys Into a Backdoor

The Hidden Danger No One Is Talking About

Passkeys are widely celebrated as the future of authentication — phishing-resistant, passwordless, and convenient. But there is a critical blind spot in how most services deploy them, and a single Cross-Site Scripting (XSS) vulnerability can exploit it to hand an attacker persistent, silent access to any account.

If malicious JavaScript runs on a page that supports passkeys, it can register an attacker-controlled passkey against the victim’s account without any user interaction. The user sees nothing. The website records a successful registration. The attacker walks away with a valid authentication backdoor.

Understanding the Passkey Registration Flow

When a user registers a passkey, JavaScript fetches registration options from the server, calls the browser API navigator.credentials.create() to generate a key pair on the authenticator, then sends the resulting public key back to the server for storage.

The server records the public key and associates it with the user’s account. Future logins work by signing a server-issued challenge with the corresponding private key — proving possession without ever transmitting a secret.

The critical piece here is attestation. Attestation allows the server (the Relying Party) to verify what kind of hardware generated the credential — a YubiKey, a TPM, a Secure Enclave. It is a cryptographic proof that a legitimate, hardware-backed authenticator was involved.

Why Most Sites Skip Attestation

Here is where the trade-off lives. Popular synced passkey providers — 1Password, Bitwarden, iCloud Keychain, Google Password Manager — do not appear in the FIDO Alliance Metadata Service. These software-based authenticators cannot produce meaningful hardware attestation because the private key is intentionally synced across multiple devices. Requiring attestation would lock out the majority of users who rely on these tools.

So most services set attestation to “none” — and that decision opens a dangerous door.

How XSS Exploits the Gap

Because the entire passkey ceremony is orchestrated by JavaScript, an attacker who achieves XSS on a page can bypass the authenticator entirely. Instead of calling navigator.credentials.create(), the malicious script generates its own key pair using the browser’s built-in crypto.subtle API, constructs a valid-looking registration payload, and submits it directly to the server’s registration endpoint.

The server, configured with attestation: “none”, has no way to distinguish this software-generated key from a legitimate authenticator-generated one. It stores the attacker’s public key against the victim’s account. The attack requires zero user interaction.

Worse still, a more sophisticated attack can act as an in-page man-in-the-middle. By hooking navigator.credentials.create(), malicious JavaScript can intercept a legitimate registration ceremony, let the authenticator generate a real key pair, but substitute the attacker’s public key before it reaches the server. The victim’s authenticator saves their passkey, the website confirms registration succeeded — but only the attacker’s passkey will ever authenticate successfully.

Why This Is Especially Dangerous

This attack vector creates persistent account access that looks entirely legitimate in audit logs. It survives password resets. It provides a clean authentication path with no suspicious indicators. For any organisation, this means identity compromise, regulatory exposure, and a security control that appeared to work while silently enabling an attacker.

Effective Defences

Defending against this threat requires layered controls. First, require step-up authentication before any passkey registration — a current password, a magic link, or another second factor. This prevents silent, JavaScript-driven registrations.

Second, deploy a strong Content Security Policy to block malicious script execution at the source. Pair this with Subresource Integrity checks on all third-party scripts to prevent supply-chain compromise.

Third, use Permissions Policy to restrict which pages and scripts can access navigator.credentials.create() and navigator.credentials.get(). Very few pages on any site genuinely need this capability.

Finally, send out-of-band notifications — via email or push — whenever a new passkey is registered on an account. An alert gives legitimate users the chance to act before an attacker can use their new backdoor.

The Bottom Line

Passkeys remain the right direction for authentication. But the security boundary they introduce only holds if everything running in the browser can be trusted. XSS breaks that trust — and without attestation enforcement, the consequences are severe and silent.

Leave a Reply

Your email address will not be published. Required fields are marked *