Im Internet Magazin las ich vor wenigen Tagen einen sehr spannenden Artikel mit dem Thema „Cross Site Request Forgery: So schützen Sie Ihren Server vor XSRF-Attacken„. Zufällig hatte ich selber im Beruf mit sehr ähnlichen Problemen zu kämpfen. Ich studierte die vorgeschlagenen Ideen und kam zu dem Folgenden Schluss: Ohne eine verbesserte Session-Verwaltung ist man als Programmierer nie sicher, ob es so funktionieren wird, wie man es gedacht hat. Der „Schuldige“ war auch schnell gefunden – die -eigene Session-Verwaltung!

Unser Problem war, dass man mehrere Fenster, Tabs oder gar Browser öffnen durfte und jede von ihnen hat unter einigen URLs einen -basierten zeitgesteuerten Autosave-Mechanismus benutzt. Hat jemand eine solche Seite mehrmals geöffnet, konnten wir nicht sicherstellen, dass nur die aktuellsten Eingaben in der Datenbank gespeichert wurden. Vor der sinnlos erhöhten Last für die Datenbank abgesehen hätte man über die selbstverschuldete Verluste der Benutzer hinwegsehen können. Aber man will ja den Benutzern entgegenkommen… (…egal wie unerfahren und naiv diese sind.)

Das Problem aus technischer Sicht und die Lösungsansätze in 3 Akten :-)

Akt 1: Mehrere Tabs oder Fenster

Pro Browser wird jeweils nur eine Session verwendet, egal wie viele Fenster oder Tabs offen sind. Mit einer „Prüfziffer“, wie sie in dem oben genannten Artikel vorgeschlagen wird, kann man zumindest einem Problem vorbeugen: dass alle AJAX-Aufrufe (die ja unbemerkt im Hintergrund zeitgesteuert) stattfinden als aktuell gelten. Jeder dieser Aufrufe erneuert die Session, so dass sie nie verfällt. Doch welcher Aufruf von welcher Seite (chronologisch gesehen) kommt, ist dem egal. Fügt man hingegen die Ausgabe von session_id() zu den Variablen in der Form der Seite, hat man zumindest eine Seite (Tab oder Fenster) auf eine bestimmte Session festgelegt. Die Session-ID sollte man natürlich beim Erstellen einer solchen Formular-Seite immer wieder wechseln. Das erreicht man mit Hilfe von session_regenerate_id() . Wird die Formularseite im anderen Tab oder Fenster aufgerufen, ist die ID der zuerst aufgerufenen Seite nicht mehr die der aktuellen Session. Das AJAX-Autosave sendet die Session-ID-Variable aus dem Formular immer mit. Wird diese vom Server mit der ID der aktuellen Session abgegliechen, fällt sofort auf, dass es ein Aufruf von einer veralteten Seite ist. Eine Warnmeldung für den Benutzer wäre als Rückmeldung sicherlich angebracht und unser Problem gelöst. (Vorausgesetzt der Autosave-Mechanismus wird ausgeschaltet und so unser Server geschont.)

Akt 2: Mehrere Browser

Sicher? Nein, nicht wirklich! Sofern es nur ein Browser ist nutzen viele Seiten dieselbe Session. Kommt ein weiterer Browser hinzu, unterhält dieser eine andere Session. Jetzt kann es die gleiche URL in zwei verschiedenen Browsern (und Sessions) geben. Nur was ist dann aktuell? Die Session-ID-Variable wird immer der ID der Session gleich sein (es gibt ja auch zwei davon). Es gibt nur einen Ausweg aus diesem Casus Perplexus. Man baut / erweitert die PHP-eigene Sessionverwaltung um und speichert zentral den User-Agent ab. Gewöhlich wird dies nur beim Login passieren. (Der alte Browser kann abgestürzt sein oder wurde geschlossen ohne Logout – man weiß es ja nie.)  Hier kann man sicher sein, dass eine neue Session starten soll.

Will man die PHP-Session-Verwaltung nicht ersetzen, empfiehlt es sich, den User-Agent (der Name des Browsers sozusagen) zentral – über die Session-Grenzen hinweg – zu speichern. Eine Datenbank wäre dafür prädestiniert. Eine entsprechende Tabelle mit User-ID als primary key und User-Agent-Name als varchar (128) als Typ MEMORY (d.h. ohne auf die Festplatte zu speichern) ist der schnellste Weg. Session-ID hilft hier nicht weiter, da es ja mehr als eine Session gibt. Die Benutzer-ID ist entscheidend. Sendet ein AJAX-Request die Daten an den Server, braucht PHP nur noch $_SERVER[‚HTTP_USER_AGENT‘] mit dem für den Benutzer eingetragenem Wert (des letzten Logins) abzugleichen und schon ist nur ein Browser der „zulässige“ (weil der aktuellste).

Akt 3: Lost in space und der Weg zurück

Doch auch die beste Technik hilft nichts, wenn der DAU (gebräuchlich für das Modell: „Dümmster Anzunehmender User“) am Werk ist. Dieser Loggt sich im Browser A ein und öffnet diese bestimmte Seite, startet gleich danach den Browser B und loggt sich dort zwei Minuten später aus (oder auch nicht). Damit ist der Browser A nicht mehr zulässig (weil laut Datenbank nicht der aktuelle) und der Browser B (vielleicht) nicht mehr in Benutzung. Auch hier braucht man einen Rettungsanker: Einen Login-ähnlichen Mechanismus, der dem Browser A wieder seine volle Macht zurück gibt. Damit der DAU nicht leichtfertig mit mehreren Browsern einen Session-Ping-Pong spielt, sollte er sein Passwort eingeben. Das macht ihn stutzig und nachdenklich – ja es nervt vielleicht (zurecht). Gleichzeitig schützt es das System, indem es einen Browser nur nach Eingabe des Passworts als den aktuellen behandelt.

Das Fazit

Wie wir gesehen haben, ist diese wirklich nützliche und einfach zu benutzende Session-Verwaltung von PHP nur halb so viel wert, wenn man an solche Situationen denkt. Einerseits Segen, andererseits ein Fluch. Weiterdenken ist Pflicht!