Zum Inhalt springen

JWT bleibt nach dem Abmelden gültig

CWE-613CWE-384OWASP A07:2021Aktualisiert 4. September 20265 Min. Lesezeit

Ein JSON Web Token wird anhand seiner Signatur geprüft und nicht in einer Datenbank nachgeschlagen. Dadurch bleibt es bis zum Ablauf gültig, auch nachdem sich der Nutzer abgemeldet, sein Passwort geändert hat oder gesperrt wurde. Mit kurzen Gültigkeitsdauern, Refresh-Token und einer Widerrufsliste stellen Sie die Kontrolle wieder her.

JSON Web Token verdanken ihre Beliebtheit einer Eigenschaft: Der Server muss nichts nachschlagen. Die Signatur stimmt oder nicht, und das genügt. Dieselbe Eigenschaft macht es schwierig, ein Token vor seinem Ablauf für ungültig zu erklären, was genau in den Momenten ein Problem ist, in denen es am nötigsten wäre. Dafür gibt es eine saubere Lösung.

Warum lässt sich ein JWT schwer widerrufen?

Bei klassischer Sitzungsverwaltung führt der Server eine Liste gültiger Sitzungen. Abmelden heißt dann: diese Zeile streichen. Bei einem JSON Web Token existiert diese Liste nicht. Das Token trägt die Angaben selbst mit sich, wer der Nutzer ist, welche Rechte er hat und bis wann es gültig ist, und es ist mit einem Schlüssel des Servers signiert. Bei jeder Anfrage prüft der Server nur diese Signatur und das Ablaufdatum.

Das ist eine bewusste Entwurfsentscheidung mit einem klaren Vorteil: Es wird kein gemeinsamer Sitzungsspeicher benötigt, was die Skalierung vereinfacht. Es bedeutet aber auch, dass der Server keine Stelle hat, an der er “dieses ist nicht mehr gültig” vermerken könnte. Solange die Signatur stimmt und das Ablaufdatum nicht erreicht ist, gilt das Token, unabhängig davon, was mit dem Konto zwischenzeitlich geschehen ist.

Das ähnelt einer Eintrittskarte mit aufgedrucktem Datum statt eines Ausweises, der gescannt wird. Der Pförtner prüft, ob sie echt ist und ob das Datum stimmt; er hat keine Möglichkeit zu wissen, dass genau diese Karte heute Morgen eingezogen wurde.

Wie wird ein nicht widerrufbares JWT ausgenutzt?

Verwundbar:

// Token mit großzügiger Gültigkeitsdauer, keinerlei Widerruf
function erstelleToken(nutzer) {
  return jwt.sign(
    { sub: nutzer.id, rolle: nutzer.rolle },
    SCHLUESSEL,
    { expiresIn: '24h' },
  );
}

app.post('/abmelden', (req, res) => {
  res.clearCookie('token');        // nur der Browser
  res.send('Abgemeldet');
});

function pruefe(req, res, next) {
  req.nutzer = jwt.verify(req.cookies.token, SCHLUESSEL);
  next();                          // Signatur stimmt, also Zugang
}

Das Abmelden entfernt das Token aus dem Browser, doch das Token selbst bleibt vierundzwanzig Stunden gültig. Wer eine Kopie besitzt, aus einer Protokolldatei, über Cross-Site Scripting oder von einem gemeinsam genutzten Rechner, kommt damit den Rest des Tages herein. Das gilt uneingeschränkt nach einem Passwortwechsel und selbst dann, wenn das Konto gesperrt oder gelöscht wurde: Das Token trägt seine eigenen Rechte mit sich und wird nirgends gegen die Wirklichkeit geprüft.

Sicher:

// Kurzes Access-Token mit eindeutiger Kennung, dazu ein widerrufbares Refresh-Token
function erstelleAccessToken(nutzer) {
  return jwt.sign(
    { sub: nutzer.id, rolle: nutzer.rolle, jti: randomUUID() },
    SCHLUESSEL,
    { expiresIn: '10m' },
  );
}

app.post('/abmelden', async (req, res) => {
  const payload = jwt.verify(req.cookies.token, SCHLUESSEL);

  // Nur bis zum ursprünglichen Ablauf merken; danach ist es überflüssig
  const rest = payload.exp - Math.floor(Date.now() / 1000);
  await cache.set(`widerrufen:${payload.jti}`, '1', { EX: rest });
  await refreshTokens.entferne(req.cookies.refresh);

  res.clearCookie('token');
  res.clearCookie('refresh');
  res.send('Abgemeldet');
});

async function pruefe(req, res, next) {
  const payload = jwt.verify(req.cookies.token, SCHLUESSEL);

  if (await cache.get(`widerrufen:${payload.jti}`)) {
    return res.status(401).send('Token widerrufen');
  }
  if (payload.iat < await nutzer.tokenUngueltigAb(payload.sub)) {
    return res.status(401).send('Erneute Anmeldung erforderlich');
  }

  req.nutzer = payload;
  next();
}

Hier geschehen drei Dinge. Das Access-Token ist nur noch zehn Minuten gültig, wodurch das Zeitfenster für Missbrauch klein bleibt. Jedes Token erhält mit jti eine eindeutige Kennung, sodass es gezielt widerrufen werden kann. Und es gibt einen Zeitstempel je Nutzer: Indem Sie bei einem Passwortwechsel tokenUngueltigAb auf jetzt setzen, fallen auf einen Schlag alle zuvor ausgegebenen Token, ohne dass Sie diese einzeln kennen müssten.

Bewahren Sie Token nicht in localStorage auf. Dieser Speicher ist mit JavaScript auslesbar, wodurch jede Cross-Site-Scripting-Schwachstelle unmittelbar das Token liefert, und gerade bei einem Token, das Sie nicht widerrufen können, ist das ein langwieriges Problem. Verwenden Sie ein Cookie mit HttpOnly, Secure und SameSite.

Welche Auswirkungen hat ein nicht widerrufbares JWT?

Der Schweregrad reicht von mittel bis hoch und hängt vor allem an der Gültigkeitsdauer. Bei Token von wenigen Minuten ist das Fenster klein; bei Token, die einen Tag oder länger halten, ist das Risiko beträchtlich.

Das Problem ist, dass Abmelden und Passwortwechsel die Handlungen sind, mit denen ein Nutzer oder Administrator die Kontrolle zurückgewinnt. Vermutet jemand, dass sein Konto kompromittiert ist, erwartet er, mit der Änderung seines Passworts den Eindringling auszusperren. Bei einem nicht widerrufbaren Token ist das nicht so: Der Angreifer behält seinen Zugang, bis das Token von selbst abläuft.

Noch schärfer wird es beim Ende eines Beschäftigungsverhältnisses oder beim Entzug von Rechten. Ein Mitarbeiter, dessen Konto morgens gesperrt wird, kann mit einem gültigen Token den Rest des Tages weiterarbeiten, als wäre nichts geschehen. Trägt das Token zudem die Rolle, bleiben auch seine alten Rechte bestehen, nachdem diese entzogen wurden. Genau dieses Szenario prüft ein Audit.

Wie spürt man ein nicht widerrufbares JWT auf?

Ein Tester meldet sich an, hält das Token fest, meldet sich ab und verwendet das Token erneut auf einer geschützten Route. Wird es akzeptiert, gibt es keinen Widerruf. Derselbe Test wird nach einem Passwortwechsel, nach dem Entzug von Rechten und nach dem Sperren des Kontos wiederholt; diese drei gehen in der Praxis häufiger schief als das Abmelden selbst.

Zusätzlich wird der Inhalt des Tokens betrachtet. Wie lang ist die Gültigkeitsdauer? Steckt darin eine jti, mit der sich gezielt widerrufen lässt? Trägt das Token Rollen oder Rechte, die damit bis zum Ablauf eingefroren sind? Wird mit Refresh-Token gearbeitet, und werden diese bei Nutzung rotiert? Ebenso wird geprüft, wo das Token aufbewahrt wird, denn eine Ablage in localStorage erhöht das Risiko eines nicht widerrufbaren Tokens erheblich. AssistSec bewertet diese Punkte im Zusammenhang, denn eine kurze Gültigkeitsdauer ohne Widerruf ist mitunter vertretbar, eine lange Gültigkeitsdauer ohne Widerruf so gut wie nie.

Wie verhindert man ein nicht widerrufbares JWT?

  • Geben Sie Access-Token eine kurze Gültigkeitsdauer in der Größenordnung von fünf bis fünfzehn Minuten.
  • Arbeiten Sie mit Refresh-Token, die Sie serverseitig speichern und damit widerrufen können.
  • Rotieren Sie Refresh-Token bei jeder Nutzung und widerrufen Sie die gesamte Familie, sobald ein altes Token erneut vorgelegt wird.
  • Geben Sie jedem Token eine eindeutige jti, damit es gezielt widerrufen werden kann.
  • Führen Sie je Nutzer einen Zeitstempel, vor dem ausgegebene Token ungültig sind, und setzen Sie ihn bei Passwort- und Rechteänderungen.
  • Bewahren Sie Token in Cookies mit HttpOnly, Secure und SameSite auf, nicht in localStorage.
  • Schreiben Sie keine Rollen oder Rechte in das Token, die sich während der Laufzeit ändern können; schlagen Sie diese besser nach.
  • Halten Sie die Widerrufsliste klein, indem widerrufene Kennungen zu ihrem ursprünglichen Ablaufzeitpunkt verfallen.

Quellen

Häufige Fragen

Verliere ich mit einer Widerrufsliste die Vorteile von JWT?

Teilweise, aber weniger als befürchtet. Sie müssen nicht jedes Token in einer Datenbank nachschlagen; es genügt, eine kurze Liste widerrufener Kennungen abzufragen, etwa in einem Cache im Arbeitsspeicher. Da diese Liste nur noch nicht abgelaufene Token enthält, bleibt sie klein.

Wie lange darf ein Access-Token gültig sein?

Fünf bis fünfzehn Minuten sind üblich. Dieses Fenster ist kurz genug, um den Schaden eines gestohlenen Tokens zu begrenzen, und lang genug, um die Zahl der Erneuerungen beherrschbar zu halten. Die längere Gültigkeit wandert zum Refresh-Token, das Sie widerrufen können.

Was ist Token-Rotation bei Refresh-Token?

Bei jeder Erneuerung gibt der Server ein neues Refresh-Token aus und macht das alte ungültig. Wird ein altes Token später dennoch vorgelegt, ist das ein starkes Signal dafür, dass jemand eine Kopie besitzt; der Server widerruft dann die gesamte Token-Familie. So wird Diebstahl zugleich begrenzt und erkennbar.

Kann ich Token nicht einfach im Browser löschen?

Das ist genau die Falle. Das Token aus dem Browserspeicher zu entfernen lässt das Token selbst unberührt: Es ist weiterhin korrekt signiert und noch nicht abgelaufen. Jeder, der eine Kopie besitzt, kommt damit weiterhin herein. Der Widerruf muss serverseitig erfolgen.

Verwandte Artikel

Zum Suchen / drücken · Esc