Passwortsicherheit bei Webanwendungen

Evaluation und Zwei-Faktor-Authentifizierung (2FA)

Braucht man eigentlich Passwörter? Es gibt viele Alternativen zur üblichen Anmeldung mit Benutzername und Passwort. Ob die Alternativen zur Anmeldung an einer bestimmten Webanwendung geeignet sind, liegt sehr an der Art der Anwendung und deren Anforderungen. Ein Web-Shop würde zum Beispiel nur wenige Kunden haben, wenn man sich ausschließlich per "Client-Zertifikat" anmelden könnte. Eine technische Universität dagegen kann erwarten, dass sich Studierende ein entsprechendes Zertifikat installieren.

Passwörter zur Authentifizierung haben den großen Vorteil, dass ein Login sehr einfach zu implementieren ist, und jeder weiß, wie so eine Art Login funktioniert. Deshalb ist ein Login mit Benutzername und Passwort auch im Jahr 2021 der Standard für Webanwendungen. Leider gibt es eine Reihe von Problemen:

  • Benutzer wählen schlechte Passwörter oder das gleiche Passwort für mehrere Dienste
  • Passwörter können mitgelesen oder weitergegeben werden
  • Passwörter können erraten werden
  • Passwörter einzugeben ist generell unbequem

Für Webanwendungen gibt es mehrere Alternativen zur Authentifizierung mit Benutzername und Passwort:

  • X.509 TLS Client-Zertifikat: Ein entsprechendes Zertifikat kann vom Benutzer selbst erzeugt werden, dann von einer CA signiert werden und nach der Installation im Webbrowser als sicheres Loginverfahren verwendet werden. Der private Teil des Zertifikats kann dabei je nach System im Browser oder im Betriebssystem sicher abgelegt werden oder alternativ auf externer Hardware. Dieses Verfahren gilt als sehr sicher. Aber sowohl die Implementierung auf Serverseite als auch die Einrichtung für Benutzer ist etwas komplexer als andere Verfahren.
  • FIDO U2F: FIDO Universal 2nd Factor (U2F (Quelle: https://en.wikipedia.org/wiki/Universal_2nd_Factor)) ist ein offener Standard, der in allen aktuellen Browsern implementiert ist. Ein Hardware-Token, der per USB, NFC oder Bluetooth angesprochen werden kann, dient als sicherer zweiter Faktor. Zur Sicherheit und Wirksamkeit von Hardware-basierter Authentifizierung wird gerne Google zitiert (Quelle: https://krebsonsecurity.com/2018/07/google-security-keys-neutralized-employee-phishing/), wo es seit der Einführung von U2F-Token für Mitarbeiteraccounts keine Accountübernahmen gegeben habe. — Günstige U2F-Hardware-Token werden schon für unter fünf Euro angeboten. In den meisten U2F-Token ist der private Schlüssel einmalig bei der Fertigung vorgegeben. Der U2F-Standard wurde durch den FIDO2-Standard überholt.
  • FIDO2 und Webauthn: FIDO2 (Quelle: https://fidoalliance.org/fido2/) ist der Nachfolger von U2F. Login mit Benutzername und Passwort wird hier komplett abgelöst durch ein Login mit Hardware-Token, der in den meisten Fällen wieder abgesichert ist mit einer PIN, so dass man zwei Faktoren zur Authentifizierung braucht. Alle aktuellen Browser unterstützen den FIDO2-Standard im Zusammenspiel mit Webauthn. Webauthn (Quelle: https://www.w3.org/TR/webauthn-1/) ist die W3C-Empfehlung zur Implementierung von starker Authentifizierung. Durch Webauthn kann der Benutzer wählen, wie das Login zu einer Anwendung für ihn am sinnvollsten erscheint. Das kann z.B. mittels Fingerabdruck sein, oder per Gesichtserkennung. Der Browser gibt nach erfolgreicher Freigabe durch den Benutzer die Schlüssel zur Authentifizierung an der Anwendung frei. Eine Art der Freigabe ist dabei die Nutzung von Schlüsseln auf einem FIDO2 Hardware-Token.

Login mit Handy-App und Push-Nachricht: Eine Handy-App bittet um Bestätigung des Logins, während man sich an einem Webbrowser einloggt. Beispiele sind die "Microsoft-Authenticator" App und "Google prompts".

Einmalpasswörter HOTP/TOTP: HMAC-based One-time Password (HOTP) ist ein Verfahren, mit dem Einmalpasswörter generiert werden. Der Benutzer tauscht ein Mal mit dem Server ein Initialpasswort aus. Danach wird nach einem offenen Standard für jedes Einmalpasswort eine weitere Iteration eines Hash-Algorithmus angewendet. Aus dem Ergebnis wird das Einmalpasswort abgeleitet. Das Time-based One-time Password (TOTP) erweitert HTOP so, dass automatisch nach einer vorgegeben Zeit - meistens 30 Sekunden - ein neues HOTP-Passwort generiert wird. Das wohl bekannteste Beispiel für TOTP ist die "Google Authenticator" App. Das Verfahren wird üblicherweise als zweiter Faktor zur Absicherung von Accounts mit Passwort-Authentifizierung verwendet.

Einmalpasswörter via SMS: Der Benutzer bekommt eine Textnachricht mit einem Einmalpasswort per SMS, das als zweiter Faktor dient. Diese Art der Authentifizierung gilt als unsicher, da ein Angreifer ggf. Zugriff auf das Telefon des Benutzers hat, oder Zugriff auf die SIM-Karte des Benutzers, oder Zugriff auf den Übertragungsweg der SMS.

Single-Sign-On (SSO) bzw. Login mit externem Dienst: Hier wird das Problem der Authentifizierung an der eigenen Anwendung zu einem anderen Anbieter ausgelagert. Beispiele sind "Login mit Apple" oder "Login mit Google". Der Aufwand für die Implementierung ist meistens recht gering, da offene Standards wie Oauth oder OpenID Connect verwendet werden oder der Anbieter Bibliotheken für die gebräuchlichsten Programmiersprachen zur Verfügung stellt. Für den Benutzer ist diese Art Login sehr komfortabel, allerdings macht man sich sowohl als Benutzer als auch als Betreiber der Webanwendung stark abhängig von dem externen Dienst. Die Praxis hat gezeigt, dass diese Art Login eher als Option zur Verfügung steht, nicht als einzige Authentifizierungsmöglichkeit.

Die angesprochenen Verfahren zur Zweifaktor-Authentifizierung (2FA) beziehen sich auf eine zweite Art der Authentifizierung als zweiten Faktor. Der Benutzer braucht also zum Authentifizieren neben einem Passwort, das er weiß, eine Hardware oder ein App auf dem Smartphone, die er besitzt. Ein zweites Passwort wäre dagegen kein zweiter Faktor.

Ein großes Problem, bei der Implementierung von 2FA ist die Authentifizierung im Fall, dass der Benutzer seinen zweiten Faktor nicht mehr hat. Vorstellbar sind z.B. Einmalpasswörter, die der Benutzer herunterladen und ausdrucken soll. Falls der Benutzer die Passwörter statt dessen nicht druckt, sondern speichert, wäre ein digitaler Angriff denkbar. Im anderen Extremfall, wenn z.B. eine Accountwiederherstellung vergessen worden wäre zu implementieren, oder wenn der Prozess zu umständlich oder fehlerhaft implementiert wäre, hätte der Benutzer effektiv seinen Account verloren. Es gilt also einen Mittelweg zu finden, je nach Anwendungsfall.

Generierung

Grundsätzlich sollte man sich überlegen, ob es sinnvoller ist, den Benutzer ein Passwort eingeben zu lassen, oder das Passwort von der Anwendung generieren zu lassen. Als Grundlage für die Überlegung empfehle ich einerseits Account-Passwörter für echte Benutzer vom Benutzer eingeben zu lassen. Damit ein Benutzer ein sicheres Passwort wählt, reicht es meistens aus, im Browser eine einfache Ampel für die Stärke des Passworts anzuzeigen. Im Zweifel kann man das Passwort serverseitig erneut auf festgelegte Regeln zur Passwortwahl prüfen und ggf. ablehnen.

Auf der anderen Seite gibt es Accounts für Software oder Hardware, die einmal konfiguriert werden und danach statisch bleiben. Beispiele dafür sind VoIP-Accounts für Telefone und API-Accounts für externe Dienste. Für diese Art Account bietet es sich an, das Passwort möglichst sicher generieren zu lassen. Der Benutzer kann dann das generierte Passwort von einem Browserfenster in das andere Browserfenster zur Konfiguration kopieren.

Falls man sich dazu entschlossen hat, Passwörter zu generieren, sollte man noch sicherstellen, dass der verwendete Zufall möglichst für Angreifer unvorhersehbar ist. Schlechte Beispiele für generierte Passwörter wären z.B. ein Hash von dem Benutzernamen, von der aktuellen Uhrzeit, von fortlaufenden Zahlen usw.. Ein sicher generiertes Passwort verwendet Zufallszahlen aus dem Entropievorrat des Systems oder einen Pseudozufallszahlengenerator, der vom System initialisiert wurde und nicht durch den Benutzer. Es lohnt sich auch ein Blick in die Dokumentation: Da die Generierung von sicheren Passwörter ein übliches Problem bei Webanwendungen ist, gibt es wahrscheinlich eine passende Funktion in der Standardbibliothek des verwendeten Frameworks.

Zuletzt sollte man noch beachten, dass es sein kann, dass der Benutzer selbst weiß, wann sein Passwort kompromittiert wurde, z.B. wenn sein Telefon oder sein Laptop geklaut wurde. Für den Fall sollte die Anwendung die Möglichkeit bieten, Passwörter neu zu generieren.

Speicherung

Es stellt sich nun die Frage, wie man Benutzer-Passwörter in einer Webanwendung speichert:

☹️ Klartext: Die Speicherung von Passwörtern im Klartext ist fast nie nötig. Eine Ausnahme ist die Verwendung von Challenge-Response-Verfahren (Quelle: https://en.wikipedia.org/wiki/Challenge%E2%80%93response_authentication), bei denen Client und Server zusammen mit weiteren Daten einen Hash über das Klartextpasswort bilden müssen.

😐 Hash: Ein einfacher kryptografischer Hash über das Passwort, z.B. MD5(Passwort), ist oft das Mittel der Wahl für Prototypen von Webanwendungen. Angriffe auf einfache Hashes sind vielfältig. Es gibt sehr effiziente Implementierungen von Hash-Funktionen und spezielle Hardware, wie sie z.B. für Bitcoin-Mining verwendet wird. Und es gibt den Angriff über Rainbow-Tables, durch die ein Teil der benötigten Rechenleistung schon vor dem Angriff ausgeführt werden kann. Ein kurzer Test zeigt, dass der unscheinbare Hash

MD5(test) = 098f6bcd4621d373cade4e832627b4f6 bei einer Google-Suche auf über 20.000 Ergebnisse trifft.

🙂 Hash mit Salt (und Pepper): Ein Salt ("Salz") ist ein zufälliger String, der zusammen mit dem Passworthash im Klartext gespeichert wird und das Passwort verlängert.

Beispiel: MD5(test || saltxxx) ("test" konkateniert mit "saltxxx")

Ein Angriff auf den Hash bzw. die Suche nach einem Passwort, das diesen Hash generiert, ist signifikant aufwändiger als ohne Salt. Um die Berechnung des Hashes weiter zu erschweren, gibt es Frameworks, die zusätzlich zu dem Salt einen weiteren String konkatenieren, der manchmal Pepper oder Applikations-Secret genannt wird. Der Pepper-String wird in der Konfigurationsdatei der Anwendung hinterlegt. Ein Angreifer, der den Inhalt der Datenbank mit Passworthashes und Salts abgreifen konnte, bräuchte zusätzlich den Pepper-String aus der Konfigurationsdatei für einen sinnvollen Angriff.

😇 Crypt-Hash-Funktionen: Es gibt eine Reihe von Passwort-Hashing-Algorithmen, die einen Angriff auf den Hash besonders erschweren. Dazu zählt Argon2 und PBKDF2, aber auch bcrypt und scrypt. Diese Verfahren führen vereinfacht gesagt den Hash-Algorithmus mit Salt mehrfach aus, so dass der Angreifer einen linear höheren Aufwand hat, ein Passwort zum Hash zu finden. Alle gängigen Frameworks und Programmiersprachen implementieren zumindest scrypt und bcrypt.

Für mehr Informationen zum Thema Passwortspeicherung lohnt sich ein Blick in das OWASP Password Storage Cheat Sheet.

Übertragung

Wie überträgt man eigentlich sicher Passwörter?

Evaluation: Als erstes sollte man sich fragen, ob die Übertragung überhaupt nötig ist. Vielleicht passt in dem angedachten Fall besser ein Challenge-Response-Verfahren, bei dem das Passwort selbst nicht übertragen wird. Vielleicht sollte man Sessions verwenden anstatt bei jedem Aufruf das Passwort zu schicken. Je weniger das Passwort übertragen wird, desto unwahrscheinlicher ist es, dass ein Angreifer das Passwort auf dem Übertragungsweg abfangen kann.

Klartext: Die Übertragung von Klartextpasswörtern ist fast nie nötig, außer man kann sich sicher sein, dass der Übertragungsweg sicher ist. Als Negativ-Beispiel ist zu erwähnen, dass es immer noch die übliche Praxis gibt, Passwort-Reset-Token oder Einmal-URLs zum Passwort-Reset unverschlüsselt per Email im Klartext zu versenden.

Klartext über TLS (HTTPS): Die Klartextübertragung von Passwörtern über einen verschlüsselten Kanal ist üblich und der Standard für die meisten Anwendungsfälle.

Challenge-Response-Verfahren: Wenn man sich sicher sein möchte, dass das Passwort nie übertragen wird, kann man Challenge-Response-Verfahren verwenden, z.B. HTTP Digest Access Authentication. Sinnvoll ist es hier einen Standard zu verwenden, der bereits implementiert ist. Aus Erfahrung kann man sagen, dass bei eigenen Implementierungen unnötig viele Fehler gemacht werden können, wodurch die Sicherheit der gesamten Applikation gemindert werden kann.

Passwort-Policy

Es gibt ein paar Denkanstöße, die dabei helfen können, passende Regeln für Passwörter zu finden:

Regelmäßig ändern? Ob es sinnvoll ist, sein Passwort alle paar Wochen ändern zu müssen, und ggf. die letzten X Passwörter oder sehr ähnliche Passwörter zu verbieten, ist eine Meinungsfrage. Dafür spricht, dass ein Angreifer, der ein Passwort herausfindet, nur bis zum Ablaufdatum Zeit für seinen Angriff hat. So können alte geknackte Passwort-Hashes ungültig sein oder auch Benutzeraccounts von ausgeschiedenen Mitarbeitern ablaufen. Andererseits kann man sich als Mensch nur wenige komplexe Passwörter merken. Wenn z.B. die Passwortregel lautet, einmal pro Monat das Passwort zu ändern und die letzten elf Passwörter gesperrt sind, dann kommen Benutzer auf einfache Algorithmen wie "Name der Katze" plus "Aktueller Monat". — Der generelle Trend in der Sicherteits-Community liegt eher in der Empfehlung, komplexe Passwörter zu verwenden, als diese ständig ändern zu müssen.

Komplexität (Quelle: https://xkcd.com/936/): Muss ein Passwort Groß-/Kleinschreibung, Zahlen und Sonderzeichen enthalten? Ein sehr langes Passwort, dass man sich einfach merken kann, hat in den meisten Fällen eine höhere Entropie als ein kurzes Passwort aus Sonderzeichen, das man sich als Mensch schlechter merken kann. Andererseits sollten die meisten Passwörter von Benutzern ohnehin von einem Passwortmanager-Programm generiert werden, so dass eine hohe Vorgabe an Komplexität keine echte Einschränkung darstellt.

Passwort-Recycling: Es gibt eigentlich keinen Grund, ein Passwort für mehr als einen Dienst zu verwenden. Im Zweifel würde einer der Dienste unbemerkt gehackt werden und der Angreifer könnte das Password bei einem anderen Dienst verwenden. Das Risiko ist einfach zu hoch.

Was ist ein gutes Passwort, bzw. was ist ein schlechtes Passwort? "Name der Firma 123" ist der Klassiker der schlechten Passwörter. Dasselbe gilt für einfache Namen und Zahlen in Kombination, z.B. "München1995". Darüber hinaus liegt der Wert des Passworts im Kontext der Anwendung. Am besten ist es, sich nicht auf die Sicherheit des Passworts zu verlassen und einen zweiten Faktor zur Authentifizierung zu verlangen, wenn es auf die Sicherheit ankommt.

Schlusswort

Wie man mit Passwörtern als Anwendungsentwickler und auch als Benutzer umgeht, ist im Endeffekt eine Frage des Risikos. Verschiedene Anwendungen haben unterschiedlichen Bedarf an Sicherheit. Eine gehackte Kreuzworträtsel-App wird wahrscheinlich nur sehr geringe Auswirkungen für den Benutzer haben (mit Ausnahme der abgegriffenen Daten); eine gehackte Bankenanwendung hat dagegen das Potential die Reputation der Bank zu schwächen. Es ist also eine wirtschaftliche Frage, ob und welche Maßnahmen im Bezug auf Passwortsicherheit getroffen werden.