WebAuthn API hijacking + passkey downgrade
TL;DR: Browser-extension or XSS hijack of navigator.credentials.* plus UA-spoofed AiTM forces a passkey login to fall back to OTP/SMS.
What it is
Passkeys (WebAuthn / FIDO2) bind a credential to (RP id, origin, authenticator). They are phishing-resistant because the browser enforces the origin check. Two attack avenues remain: hijack the API surface inside the page (extension or DOM XSS overriding navigator.credentials.get), or trigger the RP’s fallback authenticator (TOTP/SMS/email) by faking client conditions so the user is steered off passkeys and into a phishable factor that an adversary-in-the-middle proxy can capture.
Preconditions / where it applies
- RP supports passkeys but also retains a phishable fallback (OTP, SMS, email magic link)
- Either: a malicious browser extension with host permissions on the RP, a stored/reflected DOM XSS, or an AiTM proxy
- Client UA / capability signal influences which factor the RP presents
Technique
- API hijack via extension or XSS — override
navigator.credentials.get/.createbefore the page calls them:1 2 3 4 5 6
const orig = navigator.credentials.get.bind(navigator.credentials); navigator.credentials.get = async (opts) => { // forward to attacker server which calls a legitimate WebAuthn endpoint // OR cancel and force fallback by throwing NotAllowedError throw new DOMException('cancelled','NotAllowedError'); };
Throwing
NotAllowedErrormakes the page believe the authenticator declined — most RPs render “Use a different method”, revealing OTP/SMS. - Conditional UI manipulation — block
PublicKeyCredential.isConditionalMediationAvailable()to return false; the autofill passkey prompt never shows. - AiTM with UA spoof — Evilginx-style proxy forwards traffic but rewrites UA / client hints to a browser without WebAuthn support, so the RP serves the OTP screen; capture OTP and replay.
isUserVerifyingPlatformAuthenticatorAvailablelie — set it to false in the proxied page; RP downgrades to roaming auth methods or password+OTP.- Origin spoof not possible (browser enforces) — but if RP exposes
/passkey/registerto authenticated session without re-auth, attacker uses ambient session (csrf / hijacked tab) to enrol attacker’s passkey on victim’s account → silent persistent ATO. - Cross-device sync abuse — passkey synced to attacker-controlled Apple ID / Google Account if cloud account compromised; lateral move from cloud takeover to per-RP takeover.
- Recovery-flow downgrade — “Lost your passkey?” path issues SMS reset; capture via SIM swap or AiTM. See account-recovery-attacks.
Detection and defence
- Remove phishable fallbacks for high-value accounts; “passkey only” mode after enrolment.
- Require step-up (existing passkey ceremony) to add a new passkey; never allow add-passkey on a session not freshly authenticated by an existing passkey.
- Server-side: log
clientExtensionResults, RP id, attestation, authenticator AAGUID; alert on new AAGUIDs. - Detect
NotAllowedErrorspikes per user / per session. - Use Trusted Types / strict CSP to reduce XSS surface that could override
navigator.credentials. See trusted-types-bypass for limits. - Restrict extension host permissions on critical RPs; enterprise-managed extension allowlists.
- Related: 2fa-bypass, account-recovery-attacks, mv3-extension-bypass, sso-attacks.
References
- SquareX Labs — passkeys pwned — API-hijack + downgrade research
- W3C — WebAuthn L3 — spec and ceremony details
- Yubico — phishing-resistant fallback guidance — RP hardening