Irgendwann steht jeder mal vor der Aufgabe, Login-Funktionalität o.ä. zu implementieren. Ziel dieser Seite ist deshalb, einen Überblick und Startpunkt zu diesem Thema zu bieten. Schauen wir uns zuerst die Begrifflichkeiten an:
Authentisierung ist auf drei Wegen möglich:
Nachweis | Verb | Beispiel |
---|---|---|
Kenntnis einer Information | weiß etwas | Passwort |
Verwendung eines Besitztums | hat etwas | Schlüssel |
Gegenwart des Benutzers selbst | ist etwas | biometrisches Merkmal |
Hier soll es primär um Kenntnis in Form von Zugangsdaten gehen: Ein Benutzer hat einen öffentlich bekannten Benutzernamen und ein geheimes Passwort. Möchte er sich authentisieren, übergibt er beides an einen Server und dieser erlaubt oder verweigert den Zugang.
Eine der grundlegenden Regeln im Umgang mit Zugangsdaten ist, Passwörter niemals als Klartext zu speichern. Aus dem simplen Grund, dass sie gestohlen werden könnten.
Gängige Praxis ist es deshalb, kryptologische Hashfunktionen zu verwenden, um reproduzierbare Abbildungen der Passwörter zu erstellen, die sich nicht zurückrechnen lassen. Es gibt in diesem Bereich viele Algorithmen mit unterschiedlichen Stärken und Schwächen. Weit verbreitet sind beispielsweise MD5 oder SHA1, die sehr performant sind und sich damit auch zum Hashen größerer Dateien eignen.
Aber genau diese Performance wollen wir bei Zugangsdaten nicht. Denn gelangt ein Angreifer an den Hash eines Passworts, ist es mit moderner Hardware in realistischer Zeit möglich, den dazugehörigen Wert per Brute-Force-Angriff zu finden.1
Aus diesem Grund wurden Algorithmen zum Speichern von Passwörtern entwickelt, die absichtlich langsam sind, beispielsweise PBKDF2, bcrypt und scrypt (Nachtrag: mittlerweile wird Argon2 empfohlen). Ihre Langsamkeit fällt in Relation zu anderen Faktoren (Data Sanitization, Datenbankabfragen, Paketumlaufzeit) nicht ins Gewicht – vor allem, da im Normalfall nur eine Berechnung durchgeführt wird. (hash($pw) == $hash
). Brute-Force-Angriffe hingegen werden enorm erschwert – auch, da z.B. bcrypt einen einstellbaren Kostenfaktor hat, der sich auch nachträglich noch erhöhen lässt.
Da Zeit – gerade was das Knacken von Passwörtern angeht – häufig wertvoller ist als Speicher (Time-Memory Tradeoff) und um Brute-Force-Attacken zu erleichtern, werden von IT-Forensikern und Crackern sogenannte Rainbow Tables verwendet. Dabei handelt es sich letztlich einfach um eine große Datenbank vorberechneter Hash-Werte. Der vielleicht größte Verwalter dieser Tabellen ist Google. Haben Sie den MD5-Hash eines Passworts gefunden, reicht eine einfache Suche, bspw: google.de/search?q=a2b0ef405bae8278b51270241c4084c5.
Um sich dagegen zu wehren, gibt es mehrere Möglichkeiten, z.B. paternalistisch, durch das Erzwingen längerer Passwörter. Oder durch eine technische Lösung wie das Salzen (engl. salt). Dabei wird dem Passwort vor dem Hashen eine nur intern bekannte Zeichenfolge beigefügt und danach beides gespeichert.
Beim Login wird dann der Salt ausgelesen, zusammen mit dem Passwort gehasht und mit dem Soll-Hash verglichen.
Handelt es sich um eine geheime Zeichenfolge, die nicht mit dem Passwort sondern z.B. im Programmcode gespeichert wird, nennt sich der Prozess Pfeffern (engl. pepper). Der Vorteil ist, dass Angreifern der alleinige Zugang zur Datenbank nichts nützt. Beide Methoden lassen sich kombinieren und schützen auch vor Wörterbuch-Angriffen.
Ein zweiter technischer Weg ist die künstliche Verzögerung des Einloggens. Versucht sich beispielsweise ein Benutzer alle 100ms einzuloggen, stimmt da wahrscheinlich etwas nicht. Eine Art, damit umzugehen, orientiert sich am Exponential-Backoff-Algorithmus, wobei mit jedem Anmeldeversuch eine größer werdende Verzögerung eingebaut wird. Wer also viermal ein falsches Passwort eingegeben hat, kann auch ruhig mal zwei Sekunden warten. Apples iOS nutzt das beispielsweise bei Eingabe zu vieler falscher PINs.
Da HTTP zustandslos ist, müssten wir uns mit jeder Anfrage neu authentisieren. Um das komfortabler zu machen, hat Netscape vor langer Zeit das Konzept der Magic Cookies auf das Web übertragen. Bei Cookies handelt es sich um einen Token – ähnlich dem an der Garderobe im Theater – der in Textform an den Client übergeben wird. Der speichert den Cookie und schickt ihn danach bei jeder HTTP-Anfrage mit.
Wer den Token hat, hat den Zugang. Und wie jeder Token, können auch Cookies geklaut werden. Zwei einfache Möglichkeiten, das zu erschweren, sind die Flags secure
und HttpOnly
. Diese sorgen dafür, dass Cookies nur über HTTPS übertragen werden und nicht per JavaScript gelesen oder verändert werden können.
Des Weiteren ist es ratsam, niemals wichtige Daten im Cookie zu speichern. Stattdessen sollten sie auf dem Server bleiben und durch den Token im Cookie identifiziert werden.
Nun kommt es vor, dass Benutzer ihr Passwort vergessen. Da wir es gehasht haben, um zu verhindern, dass es in unbefugte Hände gelangt und es eh nicht über Kanäle versenden würden, deren Sicherheit wir nicht gewährleisten können, können wir es den Nutzer nicht zuschicken.
Gängige Praxis ist es, sich vom Besucher eine E-Mail-Adresse geben zu lassen und wenn es einen Account zu dieser gibt, ihm dahin eine Mail mit einem sogenannten Reset-Link zu schicken. Über diesen kann das aktuelle Passwort durch ein neues ersetzt werden.
Wichtig ist auch, wie damit umgegangen wird, wenn es zu einer Adresse keinen Account gibt oder das eingegebene Passwort nicht stimmt. Denn die Krux bei der Fehlerbehandlung in diesem Bereich ist, dass wir ehrliche Benutzer unterstützen wollen, ohne auch Angreifern einen Vorteil zu verschaffen. Die Meldung “Falsches Passwort” hilft, das fehlerhafte Eingabefeld zu finden, gibt einem Angreifer aber einen Hinweis darauf, dass es überhaupt einen Account zu dieser E-Mail-Adresse gibt. Und das vielleicht bei einer Seite, bei der man für den Besitz eines Accounts in Erklärungsnot geraten könnte.
Zu guter Letzt noch ein paar wichtige Stichpunkte:
Der ganze Aufwand lohnt nicht, wenn der Transportweg nicht verschlüsselt ist und die Daten damit in Klartext übermittelt werden. Logins sollte daher immer über HTTPS laufen.
Keinen Quatsch wie: “Ihr Passwort muss mindestens 1 Zahl, Klein-, Großbuchstaben und Sonderzeichen enthalten”. Und vor allem keine Regeln, die das Passwort schwächer machen wie: “Keine Leerzeichen, nur ASCII”. Denn das führt zu Security by Post-It.
Kurzum: Was auch immer Nutzer als Passwort nehmen möchten, ist gut, sofern es lang genug ist.
E-Mail-Adresse statt Benutzernamen – die sind immer frei, gut zu erinnern und gut zu validieren.
Logging ist gut. Aber loggen Sie keine Passwörter. Und halten Sie Logs generell “append only”.
Bedenken Sie auch, wo Session-Daten gespeichert werden. PHP nutzt standardmäßig einen Temp-Ordner im Dateisystem, der eventuell auch anderen Nutzern zugänglich ist.
Bieten Sie eine Logout-Option an, die alle Sessions explizit zerstört und auch den Session-Cookie löscht.
Bedenken Sie langlaufende Sessions (“Remember me”-Funktion).
Sessions nur per Cookie, nicht per Query-Parameter, da in History gespeichert.
Wenn der Benutzer sein Passwort ändert oder ähnliches: Generieren Sie eine neue Session-ID und löschen Sie die alte.
Es finden sich verschiedene Zahlen, nach denen moderne Grafikkarten 1 – 10 Milliarden MD5-Hash-Berechnungen pro Sekunde erreichen. Letztlich ergibt sich die benötigte Dauer zum Knacken aus Passwortlänge und Zeichenraum. Ein zehnstelliges Passwort aus Kleinbuchstaben ist aber mit Standardhardware an einem Tag zu schaffen. ↩