PHP-Sicherheit: Session-Fixierung

PHP ist eine äußerst beliebte Programmiersprache. Wie jede Sprache hat sie natürlich ihre Vor- und Nachteile. Oft wird dabei die mangelhafte Sicherheit genannt. Das liegt jedoch nach meiner Meinung eher an schlechten Kenntnissen der meisten Programmierer. Ein solcher Punkt ist die sogenannte Session-Fixierung.

Problemsituation
In PHP ist es einfach, eine Session zu starten. Das geht häufig über

[source:php]
session_start();
[/source]

Dabei wird geschaut, ob als Cookie oder als URL-Parameter (je nach Servereinstellung) eine Session-ID vorhanden ist. Ist sie das, wird die Session mit einer entsprechenden ID geladen. War sie bisher leer, so wird sie neu initialisiert. Ist hingegen keine ID vorhanden, so wird eine zufällige Session-ID verwendet.

Angriffszenario

Stellen wir uns einen Hacker vor, der einen Admin-Zugriff zu einem Shop haben will. Die einfachste Möglichkeit, an diesen zu gelangen, besteht darin, den Admin direkt danach zu fragen. So wäre folgende Mail an einen Shop-Inhaber denkbar:

Sehr geehrter Herr [Name],

ich habe heute Ihren Shop entdeckt und dabei ist mir aufgefallen, dass Ihr Shop eine Sicherheitslücke aufweist. Wenn Sie auf folgende URL klicken (manipulierte URL) und sich anschließend an Ihren Shop anmelden, sehen Sie, dass sich jeder Benutzer als Admin anmelden kann.

Der Shop-Besitzer, der natürlich Angst um seine Investition hat, wird ohne Nachdenken auf den Link klicken. Doch was tut dieser Link? Er leitet direkt an den Shop weiter, übergibt jedoch per Cookie oder per URL-Parameter eine festgelegte Session-ID. Den Namen der Session-ID hat der Hacker zuvor natürlich ausgekundschaftet, indem er einfach den Shop verwendete.

Das Problem ist jetzt, dass keine neue Session-ID erzeugt wird, da ja eine angegeben wurde. Der Shop-Besitzer muss sich nun lediglich anmelden und schon kann der Hacker als Admin tätig werden (er weiß die Session-ID ja).

Problemlösung
Die Lösung des Problems ist denkbar einfach: man muss beim Laden der Session lediglich prüfen, ob ein spezieller Wert schon angelegt wurde:

[source:php]
session_start();

if (!isset($_SESSION[“init”])) {
//Session ist neu – neue ID auf jeden Fall vergeben
session_regenerate_id();
$_SESSION[“init”]=true;
}
[/source]

Sehr einfach und löst das Problem. Man muss es nur wissen.

[Update]
Nach Diskussion in den Kommentaren mit Erich folgender Hinweis: Natürlich ist es möglich, dass der Angreifer eine Session öffnet und diese offen hält. Diese Session-Daten könnte dann an einen Benutzer mit erhöhten Rechten mit Hilfe eines manipulierten Links gegeben werden. Klickt dieser darauf und meldet er sich an, so hab ich als Hacker die neuen Anmelde-Daten.

Im Prinzip übernimmt also der Benutzer die korrekte Session des Hackers, womit wir einen Fall von Session-Hijacking haben. Dies kann man mit oben beschriebenen Script nicht verhindern. Es gibt mehrere Möglichkeiten, das zu verhindern:

  • Ich verwende session_regenerate_id nach dem Anmelden. Damit stell ich sicher, dass ich auf jeden Fall eine neue ID verwende und der Hacker nicht mehr darauf zugreifen kann. Die alten Session-Daten werden dabei übernommen. Das Problem ist jedoch, dass je nach Session-Inhalt Daten übernommen werden, die weiterhin kritisch sind. Diese Lösung funktioniert deshalb nur, wenn ich auch darauf achte, dass die Session übernommen sein könnte. Wenn ich z.B. meine UserID in der Session speichere, muss ich berücksichtigen, dass die falsch sein könnte. Das Verfahren stellt also lediglich sicher, dass der Hacker keinen direkten Zugriff bekommt, weil er eine andere Session hat. Sie stellt aber nicht sicher, dass dadurch das System sicherer wird.
  • Die meiner Meinung nach bessere Möglichkeit besteht darin, sich wirklich gegen Session-Hijacking komplett zu schützen. Einen 100%-Schutz gibt es dabei nicht. Es hat sich aber bewährt, einen Fingerabruck vom Benutzer zu erstellen. Dazu sammelt man in einem Array erstmal Daten über den Benutzer und bildet darüber einen Hash-Wert. [source:php]
    $fingerprintArray = array($_SERVER[‘HTTP_USER_AGENT’],substr($_SERVER[“REMOTE_ADDR”],0,7),…);
    $fingerprint = md5(serialize($fingerprintArray));
    [/source]

    Wichtig ist dabei darauf zu achten, dass die Remote-Adresse nicht komplett benutzt wird, weil sie sich bei vielen Anbietern ständig ändert.

    Vergleicht man nun bei jeder Abfrage diesen Fingerabdruck, kann man recht sicher sein, dass der Benutzer korrekt identifiziert wurde. Heißt also: anderer Fingerabdruck? Session sofort löschen.

Sicher sollte man sein, indem man beide Techniken kombiniert.

22 Kommentare zu “PHP-Sicherheit: Session-Fixierung

  1. stop, stop, stop. jetzt mal langsam.

    session_regenerate_id generiert – wie der name schon sagt – eine neue session id. also eine, die der angreifer nicht kennt. mach ich das vor dem login, hab ich eine neue session und der angreifer kommt mit “seiner” sessionid nicht in meinen adminbereich. das init-flag wird in der neuen session gesetzt; der angreifer mekrt davon nix,

    mach ich das danach, hat der angreifer alles was er braucht und ist drin, ob ich dann mit einer neuen id weitermache oder nicht interessiert den angreifer herzlich wenig, oder?

    also -> direkt als erstes machen, bevor irgendwas in die session kommt…

  2. Nein, wenn die Session schon mit entsprechender ID initialisiert wurde, dann greift dieser Schutz nicht mehr. Dann hast du aber auch kein Problem mit Session-Fixation, sondern mit Session-Hijacking. Das bedeutet, dass du zusätzlich innerhalb einer Session sicherstellen musst, dass du immer mit dem gleichen Benutzer kommunizierst. Das ist recht schwierig, weil du die IP in der Regel nicht verwenden kannst.

    Viele gehen hin und vergleichen einen Teil der IP (z.B. die ersten 3 Zahlen bleiben bei allen Zugriffen i.d.R. identisch), den Browser und was sich sonst noch über den Benutzer herausfinden lässt. Einen 100%-Schutz gibt es in dieser Hinsicht jedoch nicht.

  3. Nein, das löst das Problem nicht. Es ist ja so, dass die Session-ID in der Regel eindeutig sein sollte. Die Wahrscheinlichkeit, dass ein Hacker eine bereits verwendete ID also verwendet, ist nahezu bei 0. Das Problem bei Session-Fixation ist auch nicht, dass er die Session klaut, sondern dass er vorgibt, welche ID verwendet werden soll. Da ist es unerheblich, ob die jetzt doppelt verwendet wird order nicht.

    Dein Punkt passt eher auf Session-Hijacking: also wie verhindere ich, dass ein Hacker eine ID übernimmt, die an sich eindeutige und hoffentlich sehr zufällige ID also kopieren kann. Das löst dein Ansatz aber auch nicht. Da HTTP ein zustandsloses Protokoll ist, muss ich den echten Benutzer ja irgendwie identifizieren. Das passiert über die Session-ID. Bei jeder Anfrage wird diese mitgeschickt. Ich habe jetzt keine Möglichkeit zu unterscheiden, ob diese ID vom echten Benutzer oder von einem Hacker mitgeschickt wird. Das liegt schlicht und ergreifend daran, dass ein Hacker in der Lage ist, jede Systemvariable exakt wie der echte Benutzer zu füllen. Gehen wir davon aus, dass die IP-Adresse nicht fälschbar wäre (was sie ist), so könnte man das als Identifikator verwenden. Das ist aber nicht möglich, da manche Provider (z.B. AOL) ständig die IP ändern.

  4. @Tom: nein, das ist alles schon korrekt, hat nur mit Session-Fixation nichts zu tun. Unter der Fixierung versteht man, dass er sie erst gar nicht klaut, sondern viel einfacher, dass er vorgibt, welche zu benutzen ist. Mit einem manipulierten Link ist da dann viel zu machen.

    Was du meinst ist Session-Hijacking: Also das klauen einer Session. Dafür ist die hier vorgestellte Lösung natürlich untauglich. Um sich gegen das Klauen einer Session zu schützen musst du einen “Fingerabdruck” des Benutzers machen und diesen immer vergleichen. Zum Beispiel kannst du das eben mit Browsername, Teil der IP-Adresse (nicht die gesamte!) oder ähnliches machen.

  5. @Mathias: Bitte entschuldige, aber ich muss nochmal darauf zu sprechen kommen.

    Dein Artikel handelt um “Fixierung”. Das heißt – wo wir uns einig sind – dass der Angreifer z.B. über diesen präparierten Link eine “PHPSESSID=123123” vorgibt und PHP diese als gültige SID akzeptiert. Meldet sich der Admin nun mit dieser Session-ID an, dann hat diese ID Admin-Rechte. Gibt der Angreifer ebenfalls die “PHPSESSID=123123” in die URL an, ist er auf einmal auch Admin da er auf die selben Session-Daten zugreift.

    Aber: Dein Code von oben erzeugt bei jedem neuen Besucher eine neue, gültige SID und initialisiert diese mit “init=true”. Dies geschieht auch, wenn der Angreifer einfach die Seite besucht – ungefragt quasi. Liest er jetzt z.B. seine Cookies aus, so wird er diese initiierte SID dort auslesen können. _Diese_ seine bereits initiierte ID gibt er nun dem Admin weiter…

    Meldet sich der Admin mit der ID an, so wird dein Code feststellen, dass “init” == “true” ist und keine neue SID generieren. Somit hat der Angreifer die Session fixiert.

  6. @Erich: kein Problem, ich diskutiere gern. Bin froh, wenn jemand sich kritisch mit mir auseinandersetzt!

    Du hast recht, dass das ein Problem ist, das dieses Script nicht löst. Es hat trotzdem nichts mit Session-Fixation zu tun, denn der Admin “klaut” praktisch die Session (gut, unbewusst, aber er tut es). Ich gehe folgenden Weg, um das zu verhindern:

    Nachdem sicher eine neue Session erstellt wurde, erzeuge ich vom Benutzer einen Fingerabruck. Dazu verwende ich den User-Agent, einen Teil der IP-Adresse (die ersten Ziffern ändern sich fast nie) und was ich noch so über den Browser herausfinden kann (accepted language code, …). Darüber bilde ich einen Hash-Wert (md5(serialize(array)) und speicher den in die Session. Bei jedem Zugriff auf die Session wird dieser Hash-Wert gegengeprüft. Stimmt er nicht überein, so wird eine neue Session generiert.

    Aber wie gesagt: das ist Session-Hijacking. Nur dass der Admin die Session klaut, nicht der Hacker.

  7. Ok, lass uns etwas schneller das Ziel anvisieren, auch wenn es hart klingt und meine Absicht nicht darin besteht, Deine Arbeit schlecht zu machen:

    Behauptung:
    Dein Code schützt meist NICHT vor Session Fixierung (und auch nicht vor Hijacking). Er bietet eine trügerische Sicherheit und SCHADET allen, denen Du damit eigentlich helfen willst.

    Ergänzungen:
    Dein Code _würde_ IMMER vor Fixierung schützen, wenn er _nach_ dem Admin(!)-Login ausgeführt werden würde.

    Spiel meine Behauptung bitte z.B. mit Papier und Bleistift durch und falls Du mir dann Recht gibst, ergänze bitte Deinen Text so, dass Dein Code tatsächlich seinen Zweck erfüllen kann.

  8. Ok, ich bin lernbereit, seh aber meinen Fehler nicht: Wenn ich vom Benutzer einen Fingerabdruck gemacht habe, dann funktioniert dein Szenario nicht, es sei denn, ich weiß alle Browser-Daten sowie die verwendete IP-Adresse.

    Dein Vorschlag ist natürlich eine sichere Lösung: nach dem Anmelden wird eine neue Session erstellt. Das wird aber problematisch, wenn ich Daten aus der Session übernehmen muss. Dann muss ich geziehlt wissen, was übernommen werden soll. Sobald ich ein System habe, über das ich nicht die alleinige Kontrolle habe (weil Bibliothek), wird das nahezu unmöglich.

    Also: warum greift der Schutz gegen Session-Hijacking nicht? Warum reicht der Fingerabdruck nicht? Du widersprichst ja den meisten PHP-Security-Experten (u.a. auch dem PHP Security Consortium, die im PHP Security Guide in Version 1 eben dieses Vorgehen vorschlagen.).

    Ich lasse mich gerne korrigieren. Dein Vorgehen mit einer erzwungenen neuen ID nach dem Anmelden ist sicherlich sicher. Die Methode ist halt nur nicht immer anwendbar. Aber einfach sagen, dass meine Methode nicht funktioniert, obwohl ich ein Gegenbeispiel aufzeige, wie ich dein Angriffszenario aufheben kann, das reicht halt nicht.

  9. Du schreibst: “Wenn ich vom Benutzer einen Fingerabdruck gemacht habe, dann funktioniert dein Szenario nicht”

    – Korrekt! Nur, enthält Dein Beispielcode in der Seite keine solche Vorkehrung und Du machst Deine Leser auch nicht darauf aufmerksam, dass sie notwendig ist.

    Mein Wissen basiert auf das, was die Security-Experten sagen, unter anderem auch aus der von Dir verlinkten Seite. Chris Shiflett z.B. hat auf seine Seite auch das Stück Code, welches Du verwendest. Er schreibt allerdings dazu (http://shiflett.org/articles/session-fixation) und beantwortet damit Deine Frage “warum greift der Schutz gegen Session-Hijacking nicht?”:

    “A more sophisticated session fixation attack is one that first initiates a session on the target site, optionally keeps the session from timing out, and then executes the steps mentioned previously.

    An alternative to the approach used in Listing 2 is to call session_regenerate_id() WHENEVER A USER successfully LOGS IN, since this is the moment the session data becomes sensitive for most applications.”

    – Das ist genau das Szenario, welches ich beschreibe und empfehle: das Initiieren nach dem Login.

    Du schreibst: “Das wird aber problematisch, wenn ich Daten aus der Session übernehmen muss.”

    Chris Shiflett sagt z.B. dazu:

    “The call to session_regenerate_id() replaces the current session identifier with a new one, although IT RETAINS THE OLD SESSION information.”

    Die Daten werden von PHP mitgenommen, wenn die ID sich ändert.

  10. Ok, korrekt ist, dass kein Hinweis auf Session-Hijacking drin ist, das sollte ich auf jeden Fall machen. Der Hinweis, dass session_regenerate_id nur die ID erzeugt, ist zwar korrekt, aus meiner Sicht jedoch genauso kritisch, es kommt drauf an, was in der Session steht. Deshalb bin ich eher der Meinung: sollte ich bemerken, dass die Session von einem anderen Benutzer stammt, wird sie komplett gelöscht und neu initialisiert.

    Aber den Hinweis werde ich auf jeden Fall einbringen.

  11. OkOkOkOk….

    Ihr redet alle gegeneinander, ich würde jetzt gerne mal wissen, was WIRKLICH HILFT!

    Bei euer Diskussion kommt man sehr, sehr schnell durcheinander.
    Es wäre besser, da oben würde mehr Quellcode als Beispiel stehen, quasi ein kompletter Beispiellogin wie es gedacht ist – oder gibt es sowas schon?
    Alle anderen logintutorials befolgen nähmlich nichts von euren sachen – heißt das,diese sind unsicher?
    Und weiter:
    Wie ist das eigentlich mit dem eindeutigen Identifizieren, warum nur die ersten 3 ziffern – hat jemand anders alles identisch vom browser bis etc und nur die IP hat einen unterschied an der letzten Stelle, hat man doch auch zugriff?
    Da bringt einem das sammeln von browserdaten nichts mehr?!
    Warum da nicht die ganze IP nehmen? Man geht doch eh davon aus,
    dass man sich während des aufenthalts als user nicht neu mit dem internet verbindet.
    dies geschieht nur alle 24 stunden automatisch vom router….
    Da ist es doch nicht so schlimm und sogar besser, wenn man einfach die ganze IP nimmt…
    Wenn man den Hash-Wert dann bei jedem Seitenaufruf prüft wie z.b:
    if ($session_hash=$generierte_hash){
    echo “Du bist eingeloggt”;
    }else{
    echo “Nanana, du bist nicht eingeloggt”;
    }

    ??
    Mfg Kalle

  12. Hallo Kalle,

    nein, die ganze IP-Adresse kann man nicht verwenden, da bei einigen Providern die IP-Adresse ständig wechselt. An sich ist die IP-Adresse sogar überhaupt nicht zu verwenden, wenn die Gegenseite z.B. Anonymisierungssoftware wie TOR verwendet. Dann ändert sich die IP-Adresse nämlich unter Umständen komplett, sogar die ersten 3 Stellen.

    Die Browser-Daten sind natürlich nicht eindeutig. Man kann damit das “Klauen” einer Session aber deutlich erschweren. Eine perfekte Sicherheit geht nur mittels SSL und SSL-Cookies.

    Die meisten frei verfügbaren Login-Scripte beachten diese Sicherheitsproblematik nicht. Ausgereiftere OpenSource-Software wie z.B. phpBB machen das.

    Der obere Login ist für Session-Fixation komplett. Session-Hijacking ist nicht beachtet, in den Kommentaren nur beschrieben. Wenn ich etwas Luft habe, schreib ich mal nen Artikel für eine komplette – relativ sichere – Login-Funktion. Aber immer beachten: ohne SSL kann man das nicht zu 100%.

  13. Also es ist zwar etwas spät, aber ich beschäftige mich zur Zeit mit dem Thema, und hätte dazu noch eine Anmerkung / Klarstellung:

    Wenn ich das richtig sehe, muss man die Funktion session_regenerate_id() direkt nach dem erfolgreichen Einloggen ausführen, richtig?

    Denn die vorgegebene ID des Angreifers wird damit nicht weiter übernommen (und somit nicht mit dem Login verknüpft), so dass der Angreifer mit seiner (nun alten) ID nichts anfangen kann.

    Übergibt man als Parameter true an die Funktion, wird seit PHP 5.1 sogar die alte Session-Datei gelöscht.

    Jetzt muss man, wenn ich das richtig sehe, nur die Funktion zum richtigen Zeitpunkt aufrufen: Und zwar überprüft man beim Login die Daten auf Gültigkeit, regeneriert dann eine neue ID mit der Funktion und erst dann darf man in die Session schreiben das die Person verifiziert wurde bzw. ihren erstellten Fingerprint.

    Auch dein Code-Schnippsel mit dem Init-Wert ist nicht wirklich hilfreich. Denn wie ja auch diskutiert wurde, bringt es so absolut nichts. Sollte so nicht stehen bleiben. Ich weiß wo du den Code her hast, aber dennoch ist er nicht sinnvoll 🙂
    Denn wenn der Angreifer die ID selber erstellt auf der Website, dann gibt es auch den init Wert schon in der Session, die er der anderen Person, seinem “Opfer”, gibt.

    Dann verstehe ich noch den in deinem Update erwähnten Punkt
    “problem is, that also critical session data will remain. So you have to be aware, that there can be wrong data (e.g. wrong user id) in your session”
    nicht.

    Denn wenn der User neu einloggt, können auch keine alten Daten mehr vorhanden sein, denn er logt sich ein, Daten werden geprüft (z.B. mit der DB verglichen) und dann wird die ID neu generiert und erst DANN werden erneut die relevanten Daten in die Session geschrieben, die die Session bräuchte, wie ggf. UserID, Fingerprint etc.
    Alte Daten würden dabei ja überschrieben werden!

    Das is ein wichtiger Aspekt zur Vorbeugung der Session Fixation.
    Die Diskussion mit Erich zeigte mir, das du ggf. den Unterschied zwischen Fixation und Hijacking nicht ganz siehst?

    Bei der Fixation wird vom Angreifer zuvor eine SessionID erzeugt (gibt verschiedene Wege dafür), die er dann einem “Opfer” zuschiebt, so dass die Person die (vorgegebene) ID dann mit gültigen Daten füllt. Wird so nach dem Login, vor dem Eintragen der neuen Daten keine neue SessionID erzeugt, werden die Daten in die Session des Angreifers geschrieben, und dieser erlangt dann über diese von ihm vorgegebene ID vollen Zugriff auf den Account des “Opfers”.

    Beim Hijacking hingegen wird eine vom User erstelle ID übernommen.

    Fixation und Hijacking haben letztendlich das gleiche Ziel, den Account des “Opfers” zu übernehmen, werden aber auf unterschiedliche Weise durchgeführt und müssen so auch auf eine unterschiedliche Weise verhindert werden.

    Und dein Post oben ist etwas unübersichtlich geworden. Vllt. sollte der nochmal neu geschrieben / aufgesetzt werden?
    Aktuell kommt der Lösungsansatz nicht richtig bzw. meiner Meinung nach falsch oder zumindest zu ungenau heraus.

    An dieser Stelle möchte ich auf ein großartiges PDF-Dokument zu dem Thema verweisen: http://www.acros.si/papers/session_fixation.pdf

    Asto

  14. Ich möchte auch mal darauf hinweisen, dass, wenn der Angreifer einen manipulierten Link verwendet, er genauso gut die Browserdaten des “Opfers” auslesen und somit auch diese vortäuschen kann.

    Außerdem reicht es nicht, die ID vor dem Login neu zu generieren, da eben auch diese wieder vom Benutzer kommt und so zum Beispiel über ein gefaketes externes Login-Formular erzeugt werden könnte…

  15. Franz: Deinen 2. Absatz kann ich nicht ganz nachvollziehen.

    Du musst und darfst doch erst die Daten überprüfen und dann wenn die stimmen wird die neue ID generiert und erst dann werden die neuen Daten in der IP gesichert.

    Beim generieren der neuen ID, wird die Sitzung doch automatisch auf die neue umgestellt und die alte ist nutzlos. Somit ist die Fixation verhindert, die alte ID nutzlos.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.