| name | web-webauthn-software-authenticator |
| description | Register and authenticate against a WebAuthn/FIDO2 relying party using a self-built SOFTWARE authenticator (no hardware key) when the RP requests attestation "none" (or otherwise doesn't verify attestation trust). Load when: a login is "WebAuthn/passkey/ FIDO2", endpoints like /webauthn/register|auth/begin|finish, `navigator.credentials`, and you hold (or can leak) a registration invite/enrollment token. Authorized targets only.
|
| domain | web |
| type | technique |
| stability | learning |
| modes | ["pentest","bugbounty"] |
| severity | high |
| owasp | ["A07:2021-Auth-Failures"] |
| cwe | ["CWE-287"] |
| schema_version | 1 |
Forge a software WebAuthn authenticator (attestation: none)
When it applies
- The app authenticates with WebAuthn/FIDO2 (register + authenticate "ceremonies",
navigator.credentials.create/get, client bundles calling
/register/begin,/register/finish,/auth/begin,/auth/finish).
register/begin returns creation options with "attestation":"none" (or the RP
never validates the attestation certificate chain). None-attestation means the server
does NOT check that a real hardware authenticator vouched for the key — so a key you
generate in software is accepted.
- You can start registration: either registration is open, or you can leak an
invite/enrollment token (see
tech-mongo-agg-facet-bypass and NoSQL-injection notes).
Why it works
WebAuthn security rests on the authenticator signing a server challenge with a private
key. With attestation:none, the server trusts whatever public key the client submits at
register/finish (no proof it came from a certified device). You therefore generate your
own keypair, register its public key, then at auth/finish sign the server's challenge
with the matching private key — a completely valid assertion, because it is your key.
Method (Python: cryptography + cbor2, one cookie session)
POST /register/begin {invite_token} → options (challenge, rp.id, user.id,
pubKeyCredParams incl. -7 = ES256, attestation:"none").
- Build the authenticator:
- EC P-256 keypair. COSE public key =
cbor2.dumps({1:2, 3:-7, -1:1, -2:x, -3:y})
(kty EC2, alg ES256, crv P-256, x, y — each coord 32 bytes big-endian).
authData = sha256(rp.id) + flags + signCount(4B) + attestedCredentialData
where flags = 0x45 (UP|UV|AT) and attestedCredentialData = aaguid(16×00) + credIdLen(2B) + credId(random) + coseKey.
attestationObject = cbor2.dumps({"fmt":"none","attStmt":{}, "authData":authData}).
clientDataJSON = {"type":"webauthn.create","challenge":<opts.challenge as-is>, "origin":"http(s)://<rp host:port>","crossOrigin":false}.