Any Email You Asked For — JavaScript Bug Hunt
Modelled on the Sign in with Apple vulnerability reported by Bhavuk Jain in 2020.
- Language: JavaScript
- Layer: Backend
- Difficulty: Medium
- Concepts: Security, Auth
- Modelled on: Sign in with Apple · 2020
- Visible tests: with no email requested the account's address is used; an address the account does not own is refused
- Reward: 50 XP for a complete fix
Briefing
Modelled on the Sign in with Apple vulnerability reported by Bhavuk Jain in 2020. During sign-in, the client could ask Apple's server for an identity token for a given email address, and the server issued a validly signed token for whatever email was requested without checking that it belonged to the signed-in Apple ID. Third-party apps that trusted the token's email could have been signed in to the wrong account. Apple fixed the check on its servers and paid a $100,000 bounty.
This reconstruction's token endpoint takes the email from the client's request when one is present.
Fix issueToken so the token's email always belongs to the authenticated account.
Bug report
BUG-SIWA-EMAIL · Priority: Critical · Reported by: security
issueToken(session, request) — session.account = { id, email, relayEmail } (relayEmail is the account's private relay address), request = { clientId, email? }:
- no signed-in account (session or session.account missing) -> throw
- request.email absent -> the token's email is account.email
- request.email present -> allowed ONLY if it is account.email or account.relayEmail (the user choosing to hide their address); any other value -> throw, and no token is signed
- returns signer.sign({ sub: account.id, aud: request.clientId, email })
Observed: a signed-in user asked for a token with someone else's address in request.email and received a valid token carrying that address.
Logs
[idp] token issued sub=001234 email=victim@example.com (account email=user@example.com)The code as shipped
src/idp/tokens.js (editable)
var signer = require("./signer");
// Issues the identity token handed back to a third-party app after the user
// signs in. session.account = { id, email, relayEmail }.
exports.issueToken = function (session, request) {
var account = session.account;
var email = request.email || account.email;
return signer.sign({ sub: account.id, aud: request.clientId, email: email });
};
Read-only context: src/idp/signer.js.
Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.