PHP Session Sicherheit – Session Fixation

Session Fixation

Eine Session (Sitzung) bezeichnet eine stehende Verbindung eines Clients mit einem Server.

Bei zustandslosen Protokollen wie HTTP gibt es keine stehenden Verbindungen. Jede Kommunikation eines Clients (Browser) zu einem Webserver wird unabhängig voneinander betrachtet. Zudem können Benutzer nicht eindeutig identifiziert werden.

Für diesen Zweck werden Sessions verwendet, die den Zustand einer Webanwendung während einer Session (Sitzung) speichern können und somit eine zusammenhängende Kommunikation ermöglichen.

Sessions sind aber nicht nur nützlich, sondern auch ein beliebtes Ziel von Anfreifern. Es gibt verschiedene Angriffsmethoden. In diesem Artikel geht es um Session Fixation.

Sessions allgemein

Session Files

Durch eine Session können einem Benutzer Sitzungsdaten zugeordnet werden, die auf dem Server in sogenannte Session Files (temporär) gespeichert werden.

Solche Sitzungsdaten werden von einer Webanwendung erstellt. In PHP werden diese Daten im superglobalen Array $_SESSION übertragen.

Session-ID (SID)

Die Authentifizierung findet während einer Session über die Session-ID statt. Die Session-ID ist also wie ein Passwort, das für kurze Zeit gültig ist.

Kennt ein Angreifer die Session-ID, kann er die aktuelle Sitzung übernehmen, indem sein Client (Browser) die selbe Session-ID zum Kommunizieren mit dem Webserver verwendet.

PHP erzeugt eine Session-ID mit einer Länge von 32 Zeichen. Das Erraten ist also praktisch unmöglich und Brute-Force Attacken werden wahrscheinlich auch nicht gelingen.

Die Session-ID wird üblicherweise in Cookies oder in der URL per GET-Parameter übertragen. Die Übertragung per POST-Methode ist allerdings auch möglich.

Sessionverwaltung in PHP

Zur Initialisierung einer Session wird in PHP die Funktion session_start() verwendet.

<?php
  session_start();
  ...
?>

Wenn noch keine Session existiert, wird dem Client eine eindeutige Session-ID zugewiesen. Liefert der Client allerdings eine gültige Session-ID mit, wird die aktuelle Session wieder aufgenommen!

session_start() erzeugt eine Session oder nimmt die aktuelle wieder auf, die auf der Session-ID basiert, die mit einer Anfrage, z.B. durch GET, POST oder ein Cookie, übermittelt wurde. - Quelle: php.net

Die Session-ID kann mit der Funktion session_id() ausgelesen werden.

<?php
  ...
  echo session_id();
?>

Mit unset() können Session Variablen gelöscht werden.

<?php
  ...
  unset($_SESSION['foo']);
?>

Mit session_destroy() werden alle aktuellen Sitzungsdaten gelöscht.

<?php
  ...
  session_destroy();
?>

Bei php.net gibt es eine Liste mit weiteren Session Funktionen.

Session Fixation

Bei einer Session Fixation gibt ein Angreifer eine Session-ID vor und versucht diese fixierte Session-ID einem Benutzer unterzujubeln.

Der Vorteil von Angreifern ist, dass PHP jede beliebige übermittelte Session-ID akzeptiert.

In der folgenden Grafik wird veranschaulicht, wie die einfachste Form eines Session Fixation Angriffs aussieht. Hier wird die Session-ID in der URL übertragen.

Session Fixation Angriff

Schauen wir uns die einzelnen Schritte etwas genauer an.

1. Der Angreifer schickt eine präparierte URL in Form eines Links an sein Opfer, die zu einem Loginbereich einer Online Banking Website führt, wo das Opfer registriert ist. In der URL befindet sich die fixierte Session-ID (1234).

2. Das Opfer ruft den präparierten Link auf. An dieser Stelle erkennt der Webserver bereits die Session-ID und weist sie einer Session zu.

3. Das Opfer loggt sich mit seinen Logindaten ein.

4. Der Angreifer ruft nun mit der selben Session-ID eine Seite des Loginbereichs auf. Der Webserver erkennt die gültige Session-ID, die bereits einer Session zugewiesen ist. Die Webanwendung stellt fest, dass gültige Sitzungsdaten existieren und es sich um den eingeloggten Benutzer "Max Mustermann" handelt. Der Angreifer ist nun also mit den Daten seines Opfers eingeloggt und kann mit dem Account anstellen, was er will.

Proof-Of-Concept

Folgendes Code-Beispiel soll das ganze praktisch demonstrieren. Hierzu sind folgende Einstellungen in der php.ini notwendig, damit die Session-ID in der URL übertragen wird.

session.use_cookies = 0
session.use_only_cookies = 0
session.name = PHPSESSID
<?php
  session_start();

  if(!empty($_SESSION['userid']))
    echo 'Willkommen zurueck! ID: ' . $_SESSION['userid'];
  else
  {
    $_SESSION['userid'] = md5(uniqid(mt_rand(), true));
    echo '<p>Du bist neu hier. Deine neue ID: ' . $_SESSION['userid'] . '</p>';
    echo '<p>Session-ID: ' . session_id() . '</p>';
  }
?>

Das ganze können wir jetzt einfach mit zwei verschiedenen Browsern testen.

Browser1 = Opfer
Browser2 = Angreifer

Zuerst wird die Session-ID vom Angreifer bestimmt.

login.php?PHPSESSID=5678

Nun rufen wir die URL in Browser1 auf.

Du bist neu hier. Deine neue ID: 9eb47aed86421db352d3e2c7753c121b

Session-ID: 5678

Rufen wir die URL erneut auf, werden wir identifiziert.

Willkommen zurueck! ID: 9eb47aed86421db352d3e2c7753c121b

Wenn wir die URL nun mit der selben Session-ID (5678) in Browser2 aufrufen, werden wir ebenfalls identifiziert und erhalten die gleiche Ausgabe.

Willkommen zurueck! ID: 9eb47aed86421db352d3e2c7753c121b

Bei einem Loginscript wären wir jetzt mit Browser2 eingeloggt.

Session-ID in der URL

Werden Session-IDs in der URL übertragen, gibt es noch weitere Gefahren. Zum Beispiel die sogenannte Self Exploitation. Interessante Links werden gerne mal in Foren, Blogs, Twitter, etc. weitergegeben. Und was ist der einfachste Weg? Genau, die URL aus der Adresszeile kopieren. Jeder, der den Link aufruft, übernimmt die Session.

Sehr kritisch wird es, wenn andere Methoden (aus)genutzt werden. Zum Beispiel könnte ein Angreifer HTTP Weiterleitungen in PHP Anwendungen (z.B. Foren) ausnutzen. Dann müsste er noch nicht einmal jemanden dazu bringen, die präparierte URL aufzurufen.

Session-ID in Cookies

Wenn die Session-ID in einem Cookie übertragen wird, ist Session Fixation ohne weiteres nicht möglich. Die Betonung liegt hier auf "ohne weiteres", denn mit einer einfachen XSS Sicherheitslücke auf der Zielseite, sieht die Sache schon wieder anders aus.

Ein Angreifer könnte durch injizierten JavaScript Code sein Opfer dazu bringen das entsprechende Cookie zu setzen.

<script>document.cookie='PHPSESSID=1234';</script>

Das Kombinieren von Sicherheitslücken kann sehr effektiv sein kann.

Gegenmaßnahmen

Session Fixation ist im Gegensatz zu anderen Angriffsmethoden auf Sessions wie z.B. Session Hijacking ziemlich leicht zu verhindern.

Man könnte das Übermitteln der Session-ID per URL deaktivieren. Das geht ganz einfach, indem man den Wert von session.use_only_cookies in der php.ini auf 1 setzt.

Hat man kein Zugriff auf die php.ini, kann man das ganze auch im Script mit ini_set() regeln. Wichtig ist, dass das ganze vor dem Aufruf von session_start() geschieht.

<?php
  ini_set('session.use_only_cookies', 1);
  session_start();
  ...
?>

Das verhindert aber immer noch nicht Session Fixation, da (wie bereits erwähnt) mithilfe von anderen Sicherheitslücken die Session-ID in einem Cookie injiziert werden kann.

Session-ID Regenerierung

Ein sicherer und effizienter Schutz gegen Session Fixation ist die Session-ID Regenerierung. Bei jeder Authentifizierung ersetzen wir die Session-ID durch eine neue.

Dazu wird die Funktion session_regenerate_id() verwendet.

Die Funktion session_regenerate_id() ersetzt die aktuelle Session-ID durch eine neue und übernimmt die aktuellen Session-Informationen. - Quelle: php.net

Der Angreifer schickt eine präparierte URL mit einer fixierten Session-ID an sein "Opfer". Das "Opfer" loggt sich ein und erhält eine neue Session-ID. Die alte, fixierte Session-ID ist für den Angreifer somit nutzlos.

Bei erfolgreichem Login ersetzen wir also die Session-ID. Zudem sollten bei einem Logout immer alle Sitzungsdaten gelöscht werden.

<?php
  session_start();

  // fixierte Session-ID vom Angreifer:

  echo '<p>Alte Session-ID: ' . session_id() . '</p>';

  // Login

  ...

  // wenn Login erfolgreich, neue Session-ID und Session Variable:

  if(empty($_SESSION['user']))
  {
    session_regenerate_id();
    echo '<p>Neue Session-ID: ' . session_id() . '</p>';

    // Session Variable zur Identifikation eines eingeloggten Benutzers definieren:
    $_SESSION['user'] = true;
  }

  // bei jedem Request überprüfen, ob es sich um einen eingeloggten Benutzer handelt:
  if(!empty($_SESSION['user']))
  {
    // Benutzer ist eingeloggt, da die Session Variable existiert!
    ...
  }

  // Logout:
  session_destroy();
?>

Das war's soweit für den ersten Teil zum Thema Session Sicherheit in PHP.

Dir hat der Artikel gefallen? Dann abonniere doch einfach den RSS-Feed, damit du immer auf dem Laufenden bleibst und über neue Einträge kostenlos informiert wirst.

Diesen Artikel weiterempfehlen:
TwitterFacebookDeliciousTechnoratiMister WongWikio

9 Reaktionen zu “PHP Session Sicherheit – Session Fixation”

Kommentare (8)

1mad kommentierte am 4. Februar 2010 um 01:09 Uhr

Man sollte generell eher vor dem login die ID erneuern, nicht DANACH.

Autor

2Maik kommentierte am 4. Februar 2010 um 01:35 Uhr

@mad
In deiner Aussage sehe ich keinen Sinn. Wieso sollte man vor einem Login die SID erneuern und nicht “danach” (im selben Request)?

Auch nachzulesen in der (englischen) Wikipedia:

“Best solution: Identity Confirmation
This attack can be largely avoided by changing the session ID when users log in.”

3mad kommentierte am 10. Februar 2010 um 23:40 Uhr

@Maik
mit meiner Aussage und der Formulierung “generell” meinte ich, dass es sicherer ist, da es folgendem Szenario vorbeugt:

1. SID=1234
2. user login
3. SID=1234 ist eingeloggt
4. regnerate_id -> SID=5678
5. keine invalidierung der alten SID -> in der Session mit ID=1234 sind immernoch alle credentials gespeichert, auch wenn der user jetzt ID=5678 benutzt.

Befolgt man obiges Tutorial, so ensteht genau dieses Problem.
Laut Quellcode soll die SID durch folgenden Ausdruck erneuert werden:

session_regenerate_id();

Allerdings wird nicht erwähnt, dass man für die Invalidierung der alten SID “true” übergeben müsste. Hier der Auszug aus der Session.php :

* @param delete_old_session bool[optional]
* Whether to delete the old associated session file or not. Defaults to
* false.

Beachte : default = FALSE.

Will man sich also wirklich gegen Session Fixation schützen, so sollte man VOR dem login regenerate_id() aufrufen und/oder regenerate_id(true) nutzen.

Autor

4Maik kommentierte am 11. Februar 2010 um 20:10 Uhr

@mad
Ja, das ist schon richtig. Allerdings wird in diesem Beispiel die SID erneuert, bevor weitere Session Variablen definiert werden. Die Session Variable, die einen eingeloggten Benutzer identifiziert (in diesem Beispiel $_SESSION['user']), wird nur der neuen SID zugeordnet und nicht der alten.

Natürlich müsste man hier dann die entsprechende Variable überprüfen. Sonst wär sie ja überflüssig.

Hier mal ein vereinfachtes Beispiel: http://nopaste.us/621.html

1. Angreifer legt SID fest: SID=1234
2. User Login (SID=1234&pass=abc), neue SID, $_SESSION['eingeloggt'] = true
3. SID=1234 ist nicht eingeloggt, da die Session Variable nicht existiert. Diese ist nur der neuen SID zugeordnet, die der Angreifer nicht kennt.

5mad kommentierte am 23. Februar 2010 um 13:55 Uhr

@ Maik
Auch wenn wir hier sehr zeitversetzt kommunizieren (meinserseits), ist es doch sehr interessant, was auf diese paar Zeilen jetzt herausgekommen ist.

Meine Aussage ist so nicht gerechtfertigt : “Befolgt man obiges Tutorial, so ensteht genau dieses Problem.” : Auch wenn es nicht besonders herausgestellt wurde im eigentlichen Tutorial, so hast du natürlich recht, $_SESSION['eingeloggt'] (‘user’) wird erst nach dem Erneuern der SID gesetzt. Dies ist allerdings genau der Punkt auf den ich bei meinem allerersten Post hinaus wollte! Das Setzen von $_SESSION['eingeloggt'] (‘user’) = true ist ja das eigentliche Einloggen und erfolgt nach der Erneuerung der SID. Die Überprüfung von Benutzername und Passwort davor, sind als Authentifizierung und somit nur als Teil des gesamten Loginvorganges zu sehen.

Somit stimmen wir jetzt überein ?
“Man sollte generell eher vor dem Login die ID erneuern, nicht DANACH.”

Allerdings steht folgender interessanter Punkt noch aus:
* @param delete_old_session bool[optional]
* Whether to delete the old associated session file or not. Defaults to
* false.

Autor

6Maik kommentierte am 23. Februar 2010 um 14:45 Uhr

@mad
Dann haben wir einfach nur aneinander vorbei geschrieben und das Wort “Login” unterschiedlich definiert. Ich meinte die Authentifizierung und du das Setzen von $_SESSION['user'] = true bzw. $_SESSION['eingeloggt'] = true

Diese Variablen werden _nach_ erfolgreichem Login definiert und dienen bei weiteren Requests zur Überprüfung eines bereits eingeloggten Benutzers. Der eigentliche Login ist also meiner Meinung nach die Authentifizierung.

Aber stimmt schon, ich hätte deutlicher hervorheben sollen, dass die Session Variable einen eingeloggten Benutzer identifiziert. Werd ich gleich machen.

Und die alte Session muss man in diesem Beispiel nicht löschen, da sie keine bzw. nur unwichtige Daten enthält und sowieso später vom Server gelöscht wird. Das kommt halt immer drauf an, welche Daten in Sessions gespeichert werden.

Ansonsten eine wirklich interessante Diskussion. Sowas müsste es öfter geben. Danke für die Kommentare und jetzt sind wir uns hoffentlich einig ;-)

7Eric kommentierte am 3. März 2010 um 23:35 Uhr

Vielleicht sollte auch noch “session.cookie_httponly” erwähnt werden :)

Autor

8Maik kommentierte am 8. März 2010 um 11:26 Uhr

@Eric
Das kommt im zweiten Teil, wenn es um Session Hijacking geht ;-)

Trackbacks (1)

Kommentar hinterlassen