JWT und OAuth zum Autorisieren

von am unter blog
13 Minuten TTR (Time To Read)

Einführung

Im Folgenden will ich meine Gedanken zu JWT und Token basierter Authentifizierung darlegen.
Hin und wieder ist es total wirr, also einfach den Abschnitt skippen.
Das hier ist eher so als Skizze zu betrachten.

OAuth

OAuth ist ein Sammelsurium an Spezifikationen welche primär die Authentifizierung eines “Clienten” gegen eine “Ressource” darstellt. Dabei wird eigentlich immer davon ausgegangen, dass der Client nicht das selbe System ist wie die Ressource selbst. Prinzipiell kann man auch das Frontend vom Backend Trennen auf diese Weise.
Beim OAuth wird statt dem Passwort des Users ein Token verwendet, prinzipiell ist der Token dann ein Passwort für die Client Anwendung, jedoch eben nicht das Benutzer Passwort, denn dieses Soll nicht an eine Dritte Stelle weitergegeben werden.

Die Autorisierung bei OAuth lauft heruntergebrochen folgendermaßen ab:

  1. Die Client Software fragt bei dem Zuständigen Authorization-Server nach einer Autorisierung (Auth-Request), indem er den Nutzer auf dessen Login Maske Leitet.
  2. Nun gibt der Nutzer Seine Benutzerdaten ein, und Bestätigt die Anfrage, nun wird eine Genehmigung (Auth-Grant) an Die Clientsoftware gesendet.
  3. Per Weiterleitung ist der Benutzer nun wieder in der Client Software, diese nutzt nun den Auth-Grant um einen Zugang (Access-Token) zu erlangen.
  4. Wenn der Server den Grant für Valide befindet stellt er dem Client einen Access-Token aus welcher den Zugriff auf die Ressourcen erlaubt.
  5. Optional kann auch ein Refresh-Token mitgesendet werden, dieser Kann direkt eingelöst werden um einen Neuen Access-Token zu erlangen.

Prinzipiell ist ein Access-Token kurzlebig, und ein Refresh-Token langlebig.

JWT

Ein JWT ist ein Crypto Signiertes JSON Objekt welches zum Beispiel als Access Token verwendet werden kann. Durch die Signatur sind Modifikationen am JSON objekt erkenn bar und der Token kann als Ungültig angesehen werden.

Vorteile:

  • Session Daten können im Token Transportiert werden (z. B. Berechtigungen)
  • Der Token kann auf Ablauf der Lebenszeit geprüft werden
    • Somit Kein Prüfen in der Datenbank notwendig.
      ( :warning: zu beachten ist, dass somit ein noch nicht abgelaufener Token auch von Dritten weiter verwendet werden kann :warning: )
  • Signatur des Token kann validiert werden, um zu erkennen, ob er manipuliert wurde.
  • URL Encoded ( :warning: Nicht verschlüsselt :warning: )

Gedanken

AccessToken

  • Wenn der AccessToken nicht in einer DB nachgeschaut wird spaart das Last auf dem Server.
  • Wenn der AccessToken nirgendwo Serverseitig Festgehalten/Gespeichert wird kann er auch nicht gesperrt werden.
  • Somit sollte der AccessToken sehr Kurzlebig sein, um Missbrauch einzuschränken.

RefreshToken

  • Bildet ein “MasterPasswort”, mit dem neue AccessToken beantragt werden.
  • Sollte sicher verstaut sein.
  • Im Browser nicht sicher verstaubar.
  • Der RefreshToken wird in der Datenbank ge WhiteListet und nach gebrauch ausgetauscht.
  • Somit ist ein Abgefangener Refresh Token sperr bar.
  • Ein Abgefangener RefreshToken welcher vor dem echten Client benutzt wird, kann bis zur Sperrung immer neue RefreshToken beantragen.
  • Somit eigentlich Immernoch “MasterPasswort”.

CSRF

Bei einem Cross Site Request Forgery (CSRF oder XSRF) wird ausgenutzt, dass der Browser Atomatisch einen Cookie an eine Fremde Webseite schickt.

Beispiel

Wir Besuchen z. B. die Seite https://malicio.us, dort finden wir ein Formular welches einen request an Twitter sendet.

<form action="//twitter.com/i/tweet/create" method="post">
    <input name="status" />
    <button type="submit">Tweet It</button>
</form>

Da Twitter allem Anschein nach nur SessionIDs verwendet müsste ein solcher Angriff möglich sein, jedoch scheint dies über den Referer Unterbunden zu werden (Auch wenn das nicht empfohlen wird). Ein Curl-Replay ist hier möglich. Wenn man das Formular so irgendwo anders auf der Seite einbindet funktionier das ganze.
Da hab ich mir mal wieder das falsche Beispiel Ausgesucht…

Zum Vergleich das Login Formular von OpenStreetMap, hier sind hidden Inputs enthalten, diese Hidden Inputs sollte bei jedem Request/Nach jeder Nutzung neu erstellt werden und werden vom Client bewusst in den Request eingeschleust. Bei OSM ist es der ‘authenticity_token’, dieser wird vom Server Überprüft und entscheidet über die Ausführung des Requests…

Mögliche Umsetzung

  • CSRF Token serverseitig Persistieren um ihn zu Überprüfen erlaubt rolling Keys
  • Token so wie Twitter nicht austauschen (Stateless wie bei CSRF Cheatsheet beschrieben)
    • Token nur bei jedem ReAuth aktualisieren (CSRF-TTL == Access-TTL)

Rand Bemerkung

Was spricht gegen einen Request Generierten CSRF Token, welcher immer als HTTP-Only Cookie :cookie: und einem Response Objekt zurück gegeben wird. (Zugriff auf CSRF_Token muss synchronisiert sein)

  • Theoretisch Stateless :heavy_check_mark:
  • Theoretisch auf beiden Seiten verteilt :heavy_check_mark:
  • Sinnlos, da nirgendwo eine Historie existiert, Replay fähig :x:

Bemerkung

Eine Interessante Ansichtsweise ist, dass man bei der verwendung eine JWT keine Session habe und deshalb sich keine Gedanken über CSRF machen muss. (vgl. Angular Tips 2014)
Die SessionID könnte man wie den Token auch im LocalStorage ablegen. Korrekterweise wird gesagt, dass ein als Cookie :cookie: abgelegter Token CSRF anfällig ist.

XSS

Cross Site Scripting (XSS) beschreibt das einschläusen von “Schadsoftware” in die Webseite. Zum Beispiel könnte man das standard Verhalten von Content Management Systemen als XSS Framework bezeichnen. :woman_facepalming: Der Content wird in die Webseite integriert, mit dem einzigen Unterschied, dass der Schadcode unautorisierter Weise eingefügt wird.

Beim XSS können somit alle Informationen, welche von JS aus erreichbar sind abgefangen werden. Um sich dagegen zu schützen, sollte man seine Software dementsprechend implementieren, und den Auth Token als HTTP-Only Cookie ablegen.
Gegen einen “automatisierten” im Browser des Opfers stattfindenden Angriff wird man sich nur schwer schützen können.

Problematik des Refresh-Token

Man möchte ja nicht unbedingt, dass der User sich alle 5-15 Minuten erneut mit dem Passwort Authentifizieren muss. Deshalb gibt man dem User einen Refresh Token mit an die Hand.
Diesen Refresh-Token möchte man möglichst selten über das Netz schicken, deshalb wird er beim Client irgendwo abgelegt und nur bei einem Refresh an den Auth-Server gesendet.
Jetzt haben wir das XSS Problem, naja vom Client verwaltet ≙ per Javascript erreichbar.
Und genau hier ist nun der Knack-Punkt der ganzen Story, ich kann den Token nicht sicher verwahren und eine der beiden Möglichkeiten wird man immer missbrauchen können. :shit:
Wobei ein Angriff per XSS seitens des Programmierers weitgehend verhindert werden kann, und für Extensions und Viren des Users können die Entwickler ja nicht schuld sein.
Wir gehen natürlich stets davon aus, dass die Leitung nicht überwachbar ist. :angel:

Trade Off

  • Recht Kurzlebiger ACCESS_TOKEN ( 5 Min )
    • 5 Minuten sollten genügend Zeit geben um die DB zu entlasten
  • REFRESH_TOKEN bei jedem Refresh austauschen und für ungültig erklären (WhiteList in Kombi mit Referenz zum Access-Token)
    • DB Operationen sind erst beim Refresh nötig

Verbesserungs Vorschläge

Bei fehlerhaftem Refresh kann jeder weitere Refresh verweigert werden.

Dies ist möglich, wenn wir einen Client Identifier mit in die Tokens Integrieren. Als Idee schwebt mir da eine UUID vor, eine eindeutige Kennung des Users zum Zeitpunkt des Logins, quasi eine SessionID . Wenn nun ein Request von einem anderen als den freigegebenen REFRESH_TOKEN kommt werden alle Token dieser SessionID verworfen. Die ACCESS_TOKEN sind noch bis zum Ablauf gültig.

Noch härter kann man gehen wenn man den Refresh erst bei einem abgelaufenen Token erlaubt, ansonsten eben auch einen Lockdown macht. Der Gedanke dabei war, ein abgefangener REFRESH_TOKEN kann nicht zu früh Verwendet werden. Dies würde zumindest einen Angreifer vorerst aussperren… Jedoch unter der Annahme, dass der Netzwerk-Verkehr sicher ist, müsste die erzwungene Kombination aus Refresh und ACCESS_TOKEN ausreichend sein…

CSRF_TOKEN und ACCESS_TOKEN (CSRF und XSS Problematik)

Da wir ja nun wissen, dass irgendwie beides Sinn macht, setzen wir den CSRF_TOKEN einfach als eine uuid im ACCESS_TOKEN (welcher hier ja ein JWT ist). Dem Client schicken wir den CSRF_TOKEN aber gesondert nochmal, sodass er den ACCESS_TOKEN ja nicht zu Gesicht bekommt.

Überlegung zur REFRESH - ACCESS_TOKEN Referenz

Angenommen der ACCESS_TOKEN enthält ein REFRESHABLE_BY = REFRESH_TOKEN_ID, dies würde erlauben, dass der REFRESH_TOKEN nicht so oft ausgetauscht werden muss.

  • REFRESH_TOKEN wird NICHT Ausgetauscht Alte abgelaufene Token können mit einem noch gültigen REFRESH_TOKEN angefordert werden.
    (Replay über Laufzeit des REFRESH_TOKEN ). :-1:
    *REAUTH*
    Client: --- REAUTH(RT1)AT1 -> AT2 ----------- REAUTH(RT1)AT2 -> AT4 ---------
                 \
    Eve:            ---REAUTH(RT1)AT1 -> AT3 ----------- REAUTH(RT1)AT1 -> AT5 --
    Valid:  ---XXXXX INVALID AT1 XXXXX-----------XXXXX INVALID AT2 XXXXX---------
                                           -----------XXXXX INVALID AT3 XXXXX--
    
  • REFRESH_TOKEN wird Ausgetauscht Abgefangener Token kann nicht Replayed werden, da REFRESH_TOKEN nichtmehr erlaubt
    (Replay über Laufzeit des ACCESS_TOKEN ). :+1:
    *REAUTH*
    Client: --- REAUTH(RT1)AT1 -> AT2,RT2 --------- REAUTH(RT2)AT2 -> AT3,RT2 --
                 \
    Eve:            --- REAUTH(RT1)AT1 -> XXXX
    Valid:  ---XXXXXX INVALID AT1 XXXXXX-----------XXXXXXX INVALID AT2 XXXXXXX--
    

Somit ist das REFRESHABLE_BY hinfällig, da der Access_Token eh neu Generiert werden Sollte. Dies kommt auch einem “Session Timeout” am nähesten.

Angriff Szenarien

  • XSS (Extraktion der Credentials) Angreifer kennt REFRESH_TOKEN hat aber keinen Weg an den ACCESS_TOKEN zu kommen, nur in Kombination mit dem ACCESS_TOKEN wird ein neuer Token erstellt :heavy_check_mark:

  • CSRF (Nutzen der Credentials)
    Angreifer fehlt der Lokal vorhandene CSRF_TOKEN , sowie der REFRESH_TOKEN um anderes zu erledigen :heavy_check_mark:

  • XSS mit CSRF (Nutzen der Credentials in Kombination) Ist an und für sich immer Möglich, da der Angreifer alle benötigten Infos hat. :x:

Refresh Ablauf

  1. Client schickt “bewusst” REFRESH_TOKEN an den Server, der ACCESS_TOKEN kommt als Cookie mit.
  2. Der Server prüft Gültigkeit des Requests
    • CSRF_TOKEN gegen Wert im ACCESS_TOKEN
      (simpler Vergleich) :exclamation:
    • ACCESS_TOKEN Signatur korrekt :exclamation:
    • ACCESS_TOKEN muss abgelaufen sein
      (sonst müsste man eine revoke liste führen) :exclamation:
    • REFRESH_TOKEN Referenz zu ACCESS_TOKEN
      (Erlaubnis den Token zu erneuern) :exclamation:
    • REFRESH_TOKEN ablauf Zeitpunkt, aka Session Timeout
      (unter Umständen hier alle Token einer alten Session entfernen)
    • REFRESH_TOKEN WhiteList abgleich
  3. Erstellen der neuen TOKEN und Freigeben des neuen REFRESH_TOKEN

Im Fehlerfall wird der User zum Login geschickt…

Zusammenfassung

Die Verteilung der Token auf verschiedene Stores

  • Cookie
    • Unkritisches Zeug
  • Cookie HTTP-Only
    • ACCESS_TOKEN
      (wird über die Lebenszeit des REFRESH_TOKEN behalten)
  • LocalStorage bzw anderer JS Store
    • CSRF_TOKEN
    • REFRESH_TOKEN

Token Zusammensetzung

  • ACCESS_TOKEN
    • SESSION_ID (Die vorhin eingeführtne SessionID)
    • JWT_ID (Optional, jedoch für REFRESH benötigt)
    • CSRF_TOKEN (z. B. uuid())
    • iat (Issued At aka Timestamp)
    • CLAIMS
  • CSRF_TOKEN
    • Dieser kann ein beliebiger Wert sein, hauptsache über den ACCESS_TOKEN validierbar (wegen StateLess)
  • REFRESH_TOKEN
    • SESSION_ID (Wichtig für Session Lockdown)
    • JWT_ID (Wichtig für Whitelist)
    • VALID_FOR (ACCESS_TOKEN Referenz)
    • iat
    • CLAIMS (Werden nicht benötigt, sofern man einen ACCESS_TOKEN mit schickt)

Noch Offene Problematik

Braucht ein Login überhaubt gesondert absichern?
Sofern man keine auf Input-Date basierenden Outputs hat, sollt’s doch kein Problem sein, bis auf SQL Injection… :woman_shrugging:

security, oauth, jwt, blog