Security Research

SnitchClyde: Discord-Email-Enum als Side-Quest

Was als Neugier am Password-Reset-Token anfing, endete in einem kleinen Bash-Skript, das an einer API-Antwort den Unterschied „E-Mail schon da / nicht da“ abliest. Kein Weltuntergang — eher Lab-Notiz mit Repo-Link.

Manchmal fängt Research nicht mit einem CVE-Ticket an, sondern mit einer banalen Frage: Was steckt eigentlich in dem Token aus der Discord-Passwort-Reset-Mail? Ich wollte nur verstehen, wie die Reset-Pipeline tickt. Daraus wurde SnitchClyde — ein kleines Bash-Skript, das eine Information-Disclosure in einer Account-Endpoint-Antwort ausnutzt. Kein „Discord hat eine kritische Schwachstelle“-Fund, eher eine Spielerei mit klaren Grenzen.

Repo: github.com/Ar1sto/SnitchClyde


Discord schickt beim Passwort-Reset einen Link der Form:

https://discord.com/reset#token=<irgendwas>

Der Token sieht aus wie ein klassisches Drei-Teile-Token: Base64-Payload, Punkt, Signatur. Der vordere Teil dekodiert (bei mir) zu etwas in der Art:

{ "id": <user_id>, "email": "<account@mail.tld>" }

Der hintere Teil wirkt wie eine Signatur/MAC — und genau da endete der naive Angriffsweg: Payload verbiegen, neu Base64-kodieren, Signatur behalten oder weglassen → 400, Reset geht nicht. Erwartungstreu: ohne gültige Signatur kein Passwort-Setzen.

Parallel tauchten in den DevTools Dinge auf, die man in jedem großen SPA sieht: X-Super-Properties (Base64 mit Client-Metadaten), Requests Richtung Science/Ingest, Sentry. Interessant für Fingerprinting — aber nicht der Kern dieser Notiz.

Zwischenfazit damals: Der Reset-Token enthält ID und E-Mail im Klartext-Payload, ist aber ohne Signatur nicht missbrauchbar, um fremde Passwörter zu setzen. Die nächste Frage war: Gibt es einen anderen Endpoint, der bei „E-Mail ändern“ oder „E-Mail bestätigen“ zu viel verrät?


Abzweigung: PATCH /api/v9/users/@me

Irgendwann landete ich beim Account-Update — konkret dem PATCH auf /api/v9/users/@me, mit Body in der Nähe von:

{
  "email": "ziel@example.com",
  "email_token": "<irgendwas>",
  "password": "<eigenes-passwort>"
}

Hier wurde es spannend — nicht wegen erfolgreichem Account-Takeover, sondern wegen unterschiedlicher Fehlermeldungen.

Situation (vereinfacht, redigiert) Typische Server-Reaktion
E-Mail schon bei Discord registriert Fehlerpfad enthält u. a. EMAIL_ALREADY_REGISTERED
E-Mail (vermutlich) frei / anderer Pfad kein EMAIL_ALREADY_REGISTERED in der Antwort
Session tot / gebannt Unauthorized o. Ä.

Wichtig: Das ist kein magischer Orakel-Code ohne Kontext. Du brauchst eine eigene, gültige Session (Auth-Header). Ohne Session gibt’s nichts zu enumerieren — und mit fremder Session bist du rechtlich und ToS-mäßig im Minenfeld. Fürs Lab: nur eigene Test-Accounts, nur eigene Listen, nur ethische Szenarien.

Die Neugier war rein technisch: Warum antwortet die API so asymmetrisch? Reicht das für eine booleansche „registered?“-Probe?


Der Gedanke hinter dem „gefälschten“ email_token

Ein echter email_token aus dem Reset-Flow trägt strukturierte Daten und eine Signatur. Für die Enum-Probe brauchte ich keinen gültigen Token — im Gegenteil: Je „falscher“ der Token, desto klarer traten die E-Mail-bezogenen Fehler hervor, solange die Session selbst ok war.

Im Skript steht deshalb bewusst etwas Absurdes als Token-String (Im-Repo nachlesbar) — nicht als Magic-Byte-Sequence, sondern als „wir senden Müll, und die API redet trotzdem über die E-Mail“. Das ist der eigentliche Information-Disclosure-Aspekt: die Antwort leakt, ob die Zieladresse schon im System hängt, obwohl der Token selbst ungültig ist.

Parallel driftete der Kopf ab:

SnitchClyde ist die Spur, die ich zu einem kleinen, nachvollziehbaren Skript verdichtet habe — nicht die Summe aller Discord-Ideen im Ordner.


Was SnitchClyde macht (und was nicht)

Macht

  1. Schickt pro Ziel-E-Mail einen PATCH an /api/v9/users/@me mit Session-Token.
  2. Liest die JSON-Antwort.
  3. Wertet das Vorhandensein von EMAIL_ALREADY_REGISTERED als „E-Mail bei Discord bekannt“.
  4. Optional: Liste, CSV, etwas Geschwätzigkeit, Threads (mit dem Hinweis: Discord rate-limited brutal — im Skript steckt u. a. ein langes Sleep zwischen Requests).

Macht nicht

Kurz: Orakel auf „registered?“ mit eigener Session — mehr nicht.

Minimaler Aufruf (Konzept)

./SnitchClyde.sh --token "<DEINE_SESSION>" --email "test@example.com" --verbose

Listenmodus, Output, Threads: siehe README im Repo. Parameternamen und Sleep-Zeiten können sich ändern — Source of Truth ist GitHub, nicht dieser Artikel.


Lab-Notizen: wie ich vorgehen würde (heute)

Schritt Warum
Nur eigene / freigegebene Test-Mails Enum fremder Adressen ohne Auftrag = problematisch
Eigenen Alt-Account als Session Kein „geborgter“ Token
Eine Mail manuell im Browser reproduzieren Verstehen vor Automatisieren
Antworten speichern (redigiert) Diff zwischen registered / not registered
Rate-Limits ernst nehmen Unauthorized / Bans sind reale Nebenwirkung
Findings intern dokumentieren Nicht jeder Diff gehört öffentlich als 0-day verkauft

Wenn du so etwas findest und es reproduzierbar ist: verantwortlich melden, nicht skalieren.


Gedanken, die offen blieben

SnitchClyde bleibt absichtlich dünn: ein Bash-File, ein Endpoint, ein Bool. Wenn Discord das glättet, ist das Skript ein Museumsexponat — und das ist okay.


Fazit

Aus „was steckt im Reset-Token?“ wurde „die Account-API redet bei kaputtem email_token trotzdem über E-Mail-Registrierung“. Daraus entstand ein kleines Enum-Skript mit klarer Scope-Grenze.

Kein Meisterwerk, keine Weltnachricht — eine Research-Side-Quest mit Repo.

Ar1sto/SnitchClyde auf GitHub

Wenn du ähnliche Diffs in APIs suchst: erst manuell, dann automatisieren, und Secrets (Sessions, echte Passwörter, fremde Adressen) nie in öffentliche Gists oder Blog-Repos pasten. Diese Notiz enthält absichtlich nur redigierte Strukturen.