One link to unlink: a CSRF in OpenAI's Discord verification
A single crafted link could quietly pull any member out of OpenAI's Discord server. No password, no phishing page, no warning to the victim, just an old-fashioned cross-site request forgery on an action that should have been guarded.
CSRF is one of the oldest tricks in the book, and most people file it under "boring." It gets interesting again the moment it lands somewhere that matters. Here it was on OpenAI's own Discord verification service, and the action it forged was quietly removing people from the OpenAI Discord. One link, and a member you targeted was unlinked, with nothing on their screen to tell them it had happened.
/unlink endpoint, P3, marked Resolved and fixed.How you get into the OpenAI Discord
OpenAI runs an official Discord server, and to get in you have to prove you actually
have an OpenAI account. A small service at discord.verify.openai.com
handles that handshake: you verify, and it links your OpenAI account to your
Discord account so the server knows you belong.
If linking exists, so does its opposite: an unlink action to disconnect the two again. That is the one I went looking at, because "undo an important thing" is exactly the kind of action attackers love to find unguarded.
What CSRF actually is, in plain terms
Your browser is helpful to a fault. Once you are logged into a site, it quietly attaches your session to every request that goes to that site, no matter who started the request. Click a link, load an image, submit a form on some other page entirely, and if any of it points at a site you are logged into, your browser tags along with your logged-in identity.
Cross-site request forgery abuses that helpfulness. If a site lets a sensitive action be triggered by a request an attacker can forge, and it has no way to tell "the user really meant this" from "some other page told the browser to do this," then the attacker can make your browser perform that action as you. The standard defence is an anti-CSRF token: a secret value tied to your session that must accompany the real request, and that an attacker on another site cannot know or guess.
The endpoint had no such guard
The unlink action was triggered by an ordinary GET request to
/unlink. No unpredictable token, nothing that proved the request came from a
real click inside OpenAI's own page.
GET /unlink with no anti-CSRF token in sight.That is the whole weakness. If the only thing standing between "linked" and "unlinked" is a request anyone can reproduce, then anyone can reproduce it, on your behalf, from a page you did not write.
A state-changing action on a GET is the easiest possible CSRF, because a
GET is what your browser does just by loading a URL. No form even required, an
image tag, a redirect, or a link the victim clicks is enough to fire it.
Building the proof of concept
Burp Suite will generate a CSRF proof of concept for you: a tiny HTML page that, when
opened, submits the request to the target on its own. Point it at /unlink
and it produces a self-contained page.
/unlink by itself.
In practice the attacker never shows a button. They wrap the same request in a link or an auto-submitting page and get it in front of the target: a Discord DM, an email, a comment, anywhere a curious member might click.
And the victim is out
Loading the page carried the action through, and the service happily reported the job done: the account had been de-linked.
Why it matters
- Silent removal. Any member could be pulled out of the OpenAI Discord without their knowledge or consent, just by getting them to open a link.
- You can pick your target. Aim it at moderators or admins and you can disrupt the people who actually keep a large community running.
- It scales. The same link works on anyone, so a single crafted page dropped in the right channel can knock out members at will.
The fix
- Require an anti-CSRF token. Tie a secret, per-session token to the unlink action and reject any request without it. This is the direct fix, and it is the one that closed this report.
- Do not change state on a GET. An action that unlinks accounts should be a
POSTat least, so it cannot be fired by merely loading a URL. - Set cookies to SameSite.
SameSite=LaxorStrictstops the browser from carrying your session on cross-site requests, which cuts off the whole class.
Key takeaways
- CSRF is only "boring" until it sits on an action that matters. "Undo an account link" is exactly such an action.
- State-changing work on a
GETwith no token is the easiest CSRF there is, because just loading a URL is enough to trigger it. - The defence is a per-session secret the attacker cannot know: an anti-CSRF token, backed by SameSite cookies and POST-only state changes.
- Always look at the reverse of a sensitive action. If linking is guarded but unlinking is not, unlinking is where the bug lives.