PHP/Anwendung und Praxis/Loginsystem

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Ein Login-System hat mit drei Sachen zu tun:

  1. übermittelte Nutzerdaten empfangen und verarbeiten
  2. Passwortvergleich mit "verschlüsselten" Nutzerpasswörtern
  3. Umgang mit Sessions

Hinweis

Wenn (was regelmäßig der Fall ist) Passwörter nicht im Klartext gespeichert werden sollen, dann verwendet man keine Verschlüsselung sondern eine kryptologische Hash-Funktion. Das Ergebnis einer solchen Funktion nennt man Passwort-Hash, oder kurz Hash. Der Unterschied besteht darin, dass man ein verschlüsseltes Passwort wieder entschlüsseln kann, einen Hash aber nicht, da er mittels einer Einwegfunktion erzeugt wurde.


Fangen wir mit dem dritten Aspekt an:

Sessions

Mit dem HyperText-TransportProtokoll gibt es ein technisches Problem: Die Verbindung wird immer vom Client (in der Regel ein Browser) zum Server hin aufgebaut, eine Anfrage (der sogenannte Request) abgesetzt, die Antwort vom Server (die sogenannte Response) entgegen genommen und die Verbindung wieder getrennt. Wie soll da eine Anmeldung am Server technisch funktionieren, wenn die Verbindung nach dem Erhalt einer Serverantwort wieder getrennt wird?

Da HTTP ein sogenanntes zustandsloses Protokoll ist, braucht es eine Lösung, wie der Server einen Client wiedererkennen kann, wenn sich dieser bei einer neuen Verbindung mit einem neuen Request meldet. Das geschieht durch das Zuteilen einer Art Geheimnummer, einer eindeutig zuordbaren Folge von Ziffern und Buchstaben. Diese nennt man auch Session-ID. Anhand dieser ID kann der Server einen Client eindeutig wiedererkennen.

Die Session-ID

Das Übermitteln einer Session-ID kann prinzipiell auf zwei Arten erfolgen:

  1. als URL-Parameter
  2. als Cookie-Wert

Stellen wir uns vor, ein anderer Client "errät" die Session-ID, dann kann er sich beim Server als derselbe Client ausgeben und von möglichen Privilegien profitieren, die der ursprüngliche Client eventuell gewährt bekommen hat. Daher ist die Session-ID etwas, das man möglichst nicht offen vor sich her trägt.

Eine Möglichkeit der Geheimhaltung ist also, den URL-Parameter nicht zu nutzen, da dieser in Server-Logs und auch in der Adresszeile des Browser erscheint. Man kann sogar Links oder Bookmarks generieren, die diese Identität enthalten, und diese Links dann an andere Nutzer weitergeben, damit diese vielleicht unberechtigt in den Genuss von Privilegien kommen können.

Wenn der Server bei jedem Aufruf die Session-ID ändert, dann kann der Client damit umgehen, indem er beim nächsten Request die neue Session-ID benutzt, andere Clients aber, die weiterhin die alte ID benutzen, haben beim nächsten Aufruf keine gültige ID mehr - oder der ursprüngliche Nutzer (in dem Fall sollte er schleunigst nach der undichten Stelle suchen und nach dem Abdichten sein Passwort ändern).

Wenn man URL-Parameter verstecken will, dann sollte man die zweite Lösung wählen und die Session-ID in einem Cookie speichern. Das setzt natürlich voraus, dass der Nutzer das Speichern von Cookies für diese Domain (im Prinzip diese Website) erlaubt. Aber allein das Nutzen eines Cookies anstatt eines URL-Parameters macht die Sache noch nicht sicher.

Grundsätzlich ist jedoch alles sehr unsicher, wenn die Verbindung zwischen Client und Server unverschlüsselt erfolgt, wenn also nicht HTTPS, sondern nur HTTP zum Einsatz kommt. Eine SSL- oder besser TSL-verschlüsselte Verbindung ist die Grundvoraussetzung für ein einigermaßen sicheres Loginsystem.

Das Manual von PHP widmet ein ganzes Kapitel zum Thema "Sessions und Sicherheit": englische Version (garantiert aktuell), deutsche Version.

Sessions in PHP

In PHP gibt es einige Funktionen, um mit Sessions umzugehen. Im Wesentlichen verwendet man die Funktion session_start(), um eine Session überhaupt zu haben. Es gibt eine PHP-Einstellung, dass Sessions automatisch bei einem Request gestartet werden sollen (session.auto_start), jedoch kann diese PHP-Einstellung hinderlich sein, weshalb man sie besser deaktiviert lässt, was auch die empfohlene Voreinstellung ist. Hinderlich ist diese Einstellung dann, wenn man bei Besuchern nicht automatisch ein Cookie setzen möchte, nur weil diese die Website überhaupt aufgerufen haben, speziell dann nicht, wenn sie nicht auf der Login-Seite sind.

Ist eine Session gestartet worden, kann man das superglobale Array $_SESSION dazu benutzen, Daten für diesen Besucher darin zu speichern, z.B. ob sich der Besucher erfolgreich angemeldet hat.

Anmeldedaten via Formular

Der Nutzer soll Anmeldedaten an den Server übermitteln. Dazu braucht es ein geeignetes Formular, in welchem die Daten erhoben und versandt werden können. Da wir oben schon erkannt haben, dass sensible Daten nicht in URL-Parametern stehen sollten, ist dieses Formular zwingend mit der POST-Methode einzurichten:

Beispiel: Login-Formular mit ungeordneter Liste ansehen …
<form action="https://example.org/login.php" method="post">
  <ul>
    <li>
      <label for="login">Benutzer</label>
      <input id="login" name="login">
    </li>
    <li>
      <label for="pass">Passwort</label>
      <input id="pass" name="pass" type="password">
    </li>
    <li>
      <button>anmelden</button>
    </li>
  </ul>
</form>
Mit dem method-Attribut lässt sich die POST-Methode des Datenversandes einstellen. Als Zieladresse wurde explizit eine Seite mit einer verschlüsselten HTTP-Verbindung angegeben.

Auf der Serverseite müssen diese Daten nun empfangen und ausgewertet werden.

Passwort prüfen

Eine zentrale Angelegenheit bei einem Login ist das Prüfen eines Passworts. Eine Web-Anwendung will bei einem Anmeldeversuch ermitteln, ob die Kombination aus Benutzernamen und Passwort korrekt ist. Dazu braucht sie lesenden Zugriff auf Anmeldedaten. Diese können auf unterschiedlichste Art gespeichert sein, sei es in einer Datenbank, in einer speziellen Konfigurationsdatei oder auf sonst eine Weise.

Ein in der Fachpresse immer wiederkehrendes Thema sind Einbrüche in Systeme, bei denen dann Zugangsdaten erbeutet wurden. Besonders gravierend sind solche Vorfälle, wenn die Passwörter der Nutzer im Klartext gespeichert wurden, da dann die Kombinationen aus Benutzernamen und Passwort bekannt geworden sind. Daher wird allgemein empfohlen, die Passwörter nur als Hashes zu speichern, damit in einem Fall, in dem ein Angreifer die Zugangsdaten lesen kann, dieser die Passwörter trotzdem nicht erfährt, da sich ein Hash nicht umkehren und das Passwort nicht wieder in den Klartext zurückwandeln lässt.

Wie man Passwörter richtig gehasht ablegt, muss man den Experten überlassen, die in PHP eine dafür eingerichtete Funktion bereitstellen: password_hash(). Warum man solcherlei Verfahren unbedingt Experten überlassen muss, liegt an der hohen Komplexität des Themas: (SELFHTML-Blog) Eine selbst gestrickte Verschlüsselung kann leicht teuer werden.

Beispiel: Passwort richtig verschlüsseln
<?php
 
$encrypted_password = password_hash('Klartext', PASSWORD_DEFAULT);
 
?>
Die Funktion password_hash nimmt als ersten Parameter das zu hashende Passwort, als zweiten Parameter eine Konstante für den Passwort-Algorithmus entgegen. Üblicherweise sollten Sie hier PASSWORD_DEFAULT verwenden, damit die Funktion die stärkste verfügbare Hashing-Funktion benutzt.

gehashte Passwörter prüfen

Man kann gehashte Passwörter nicht entschlüsseln. Das ist der Sinn, warum man Passwörter nur gehasht abspeichert. Aber wie will man dann testen, ob das von einem Benutzer eingegebene Passwort das richtige war?

Die Lösung ist folgende: Die Hashing-Funktion kann das vom Benutzer eingegebene Passwort ebenfalls hashen, um das Ergebnis mit dem gespeicherten Passwort-Hash zu vergleichen. Damit das Hashing des eingegebenen Passwortes mit dem gespeicherten Passwort-Hash vergleichbar wird, wird der gespeicherte Hash als eine Art Zutat in das Hashing des eingegebenen Passwortes gegeben. Ist das Ergebnis anschließend identisch, ist bewiesen, dass der Benutzer das richtige Passwort eingegeben hat.

Um diesen Vorgang dem Programmierer zu erleichtern, gibt es in PHP dafür die Funktion password_verify(), die zwei Parameter nimmt, nämlich das zu prüfende Passwort, und den gespeicherten Passwort-Hash:

Beispiel: Passwort auf Richtigkeit prüfen
<?php
 
$hashed_password = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';
 
if ( password_verify($_POST['pass'], $hashed_password) ) {
  // Passwort war richtig.
} else {
  // Passwort war falsch.
}
 
?>
In der Variablen $hashed_password steht ein gehashtes Passwort. Im if-Statement wird die Funktion password_verify aufgerufen, der als erster Parameter das vom Client übertragene Passwort (siehe Formular oben mit pass als Name für das Passwort) übergeben wird, welches mit dem zweiten Parameter, dem gespeicherten Passwort-Hash, getestet wird.

Kein Loginsystem

Wir haben in diesem Tutorial gelernt, wo überall offensichtliche Stolpersteine hinsichtlich der Sicherheit eines Systems mit Login-Funktionalität liegen können. Wenn man dann noch in Betracht zieht, dass der Umgang mit vom Benutzer eingegebenen Daten ein prinzipielles Sicherheitsrisiko darstellt, dann muss man Laien dringend davon abraten, selbstgestrickte Lösungen zu basteln, wenn sie dadurch wichtige Prozesse wie z.B. den Webshop einer Firma akut gefährden. Selbst erfahrene Programmierer können bei der Verarbeitung von Nutzereingaben Fehler machen, indem sie z.B. wichtige Kontextwechsel nicht beachten, oder an anderer Stelle ungeprüft Daten einfach übernehmen und so eine Sicherheitslücke schaffen.

Wie eine Login-Prüfung aussehen könnte, wurde bereits im Artikel Formulardaten serverseitig auswerten mit dem Login-Beispiel exemplarisch vorgeführt. Ein vollständiges Login-Script möchten wir aber aus Sicherheitsbedenken nicht anbieten, sondern den Einsatz von erprobten Frameworks empfehlen, bei denen sich Experten ständig um neue Erkenntnisse und bekannt gewordene Sicherheitslücken kümmern, sodass Ihnen nur noch das ständige Aktualisieren der Framework-Software bleibt.

ToDo (weitere ToDos)

erprobte Frameworks empfehlen -- Matthias (Diskussion) 18:15, 12. Jul. 2016 (CEST) --
Meine Werkzeuge
Namensräume

Varianten
Aktionen
Übersicht
Schnell‑Index
Mitmachen
Werkzeuge
Spenden
SELFHTML