The product offers "log in with a QR code." A desktop browser opens a pending session. A phone that has paired before holds long-lived pairing cookies and confirms that session.
The confirm URL was a GET. It did not carry an anti-forgery token. It did not check that the pending session belonged to the person confirming it. The pairing cookies were not HttpOnly and defaulted to Lax, so a cross-site top-level navigation sent them.
Sequence, on accounts we controlled:
- Attacker opens the login page and receives a pending session id. No account required.
- The browser that holds pairing cookies from an earlier QR login requests the confirm URL for that pending id.
- The attacker's poll receives a one-time login URL.
- Following it sets a full session cookie.
A confirm with no pairing cookies did not complete the login.
What we proved
End-to-end session on a test account: pending id we created, confirm from the paired browser, authenticated session on the waiting side.
What this is not
Not a takeover of an unrelated customer in production. The victim condition is "this browser completed QR login once," and the proof used a paired test browser we held. The missing check is the report.
Why a scanner misses it
CSRF scanners look for cookie-authenticated POSTs. This is a GET that completes a login, and the privileged cookie is the pairing cookie, not the session under test.
Buyer takeaway: A device-pairing confirm is a login. It needs a token bound to the pending session, and it should not be a GET.