Kryptoanalyse von Session-IDs

Kryptoanalyse von Session-IDs

Marc Ruef
by Marc Ruef
time to read: 15 minutes

Session-IDs – von nun an durch SID abgekürzt – sind zu einem elementaren Bestandteil moderner Applikationen geworden. Anhand derer werden etablierte und oftmals authentisierte Sitzungen als solche identifiziert. Durch den Austausch der SID zwischen Client und Server kann damit die Authentizität der Sitzung ohne erneute Authentisierung bestätigt werden. Eine SID ist quasi ein temporär ausgehandeltes Passwort.

Aus diesem Grund sind SIDs jedoch auch sehr interessant für Angreifer, denn der Diebstahl einer solchen kann in vielen Fällen dazu genutzt werden, um sich in eine legitime Sitzung ohne Durchlaufen der Authentisierung einzuklinken und sie zu übernehmen (Session-Hijacking). Der Diebstahl einer geheim gehaltenen SID ist jedoch gar nicht erst erforderlich, wenn sich eine legitime SID erraten lässt, da die Generierung derer nicht zufällig genug geschieht (Session-ID Guessing).

Die Kryptoanalyse einer SID kann dabei helfen, anhand wiederkehrender Muster eine schwache oder fehlende Entropie auszumachen. Nachfolgend sollen einige initiale Techniken beschrieben werden, wie die Herkunft einer SID ermittelt werden kann:

Zeitwerte erkennen

Programmierer, die sich nicht mit Kryptologie auskennen, pflegen komplexe Zahlen mit Zufälligkeit zu verwechseln: Nur weil eine Zahl sehr gross ist, heisst es nicht, dass sie nicht erraten werden kann.

Zeitstempel werden gerne als Teil einer SID verwendet, da a) das Unix-Format einerseits eine komplex anmutende Zahl generieren lässt (10 Stellen) und b) “stets” ein anderer Zahlenwert erwartet werden kann. Eine simple, lediglich auf einem Unix-Zeitstempel basierende SID generiert sich in PHP durch den folgenden Aufruf von md5() (ein ähnlicher Ansatz wurde im PHP-Projekt übrigens für die allgemeine Verbesserung der Entropie des RNG auch angegangen):

$sid = md5(time());

Und in der Tat, praktisch jedes Mal, wenn die Applikation aufgerufen bzw. die SID durch time() generiert wird, wird diese durch einen gänzlich anderen Hashwert dargestellt. Doch eine sorgfältige Beobachtung des Timing-Verhaltens innerhalb der Applikation hilft solche Implementierungen zu erkennen.

So kann versucht werden zwei Zugriffe zeitgleich auf die Generierung der SID ausüben zu lassen. Gelingt dies, es muss in diesem Fall auf die Sekunde genau geschehen, sollten die gleichen Werte für zwei vermeintlich unterschiedliche SIDs generiert werden. Damit ist eine Kollision vorhanden und bewiesen:

ID Uhrzeit Timestamp Hash Kollision
0 17.03.10 13:38:54 1268833134 8b14b7862337b8d0e719c00bdd945b9e
1 17.03.10 13:38:55 1268833135 72faa7475c51b4f296849e9ef37cd4dc
2 17.03.10 13:38:55 1268833135 72faa7475c51b4f296849e9ef37cd4dc ⊆ id1
3 17.03.10 13:38:56 1268833136 9168ddaa6ed1352c6e793d63799895fa
4 17.03.10 13:38:56 1268833136 9168ddaa6ed1352c6e793d63799895fa ⊆ id3

Als Angreifer erschliessen sich SIDs auf der Basis von time() als Vorteil, da die Ausgangswerte stets die gleiche Struktur haben (10 Stellen und nur nummerische Zeichen), mit jeder Zeiteinheit (z.B. Sekunde) um 1 inkrementiert werden und sich in einem voraussehbaren Rahmen bewegen. Eine Bruteforce-Attacke auf die SID kann daher mit einem vorhersehbaren, linearen und endlichen Aufwand geschehen.

Quellen identifizieren

Anwendungen, die in grossen Netzen oder dem Internet bereitgestellt werden und entsprechend durch eine Vielzahl unterschiedlicher Anwender genutzt werden, greifen oftmals zur Generierung der SID auf Merkmale des Clients zurück. So zum Beispiel die Quell-IP-Adresse, die vom Client für den jeweiligen Zugriff genutzt wird. Eine PHP-Implementierung gestaltet sich wie folgt:

$sid = md5($_SERVER['REMOTE_ADDR']);

Applikationsentwickler gehen oftmals davon aus, dass a) man mit eindeutigen IP-Adressen rechnen kann und b) die IP-Adresse während einer Sitzung nicht zu wechseln pflegt. Nun, theoretisch ist beides falsch:

Um derlei Implementierungen ausmachen zu können, ist der Zugriff durch unterschiedliche Systeme erforderlich. Dabei muss sich nicht zwingend auf die IP-Adresse fokussiert werden. Die nachfolgend gezeigte Tabelle listet auf, welche Werte alternativ oder zusätzlich für eine clientbasierte Unterscheidung herangezogen werden können.

Merkmal Zugriff mit PHP Verbreitung
IP-Adresse $_SERVER['REMOTE_ADDR'] hoch
Hostname $_SERVER['REMOTE_HOST'] mittel
Browser $_SERVER['HTTP_USER_AGENT'] mittel
Referer $_SERVER['HTTP_REFERER'] gering

Einem ähnlichen Prinzip wird gefolgt, wenn SID aus Benutzernamen oder Mailadressen generiert werden. Gerade letztere wird durch Applikationsentwickler manchmal als geheim verstanden und deshalb nicht damit gerechnet, dass das Wissen um diese die Sicherheit einer Session beeinträchtigen könnte.

Statische Werte eingrenzen

Es müssen jedoch noch nicht einmal dynamische Werte, die an Zeit oder Kommunikationspartner gebunden sind, eingesetzt werden. So manches Produkt pflegt sehr statische und damit unweigerlich voraussagbare Werte zu verwenden. Halt einfach dann, wenn zum Beispiel eine geheime Zeichenkette als Ausgangswert verwendet wird. Man kann hier von einem Standardpasswort (Default Password) sprechen. Beispiel:

$sid = md5("SECRET_SESS_KEY_2010");

Statische Werte lassen sich sehr einfach erkennen, sofern sich ausschliesslich auf diese abgestützt wird. Durch klassische Bruteforce-Attacken oder Rainbow-Tables lassen sich gerade Wörter aus dem täglichen Sprachgebrauch unkompliziert als solche identifizieren. Oftmals sind die statischen Werte jedoch lediglich ein Teil eines Original-Werts, der ebenfalls dynamische bzw. individuelle Komponenten beinhaltet. Typische Beispiele:

$sid = md5("SECRET" . time());
$sid = md5("SECRET" . $_SERVER['REMOTE_ADDR']);

Das Identifizieren der Verflechtung von statischen und dynamischen Grundwerten ist nicht einfach. Werden diese gar in einem einzigen Hash-Zusammengeführt, der einen Avalanche-Effekt garantiert, ist die Entbündelung alleine anhand des Hashwerts nicht ohne weiteres möglich. In diesem Fall kann sich nur auf Bruteforce-Attacken abgestützt werden, wobei die Struktur der Grunddaten – beispielsweise [STRING]_[TIME] – berücksichtigt werden muss.

Hashing von Teilinformationen

Anders sieht es jedoch aus, wenn die Teilinformationen separat gehasht werden und das Resultat zum Schluss zusammengeführt wird. Nachfolgend werden die beiden separaten Hashwerte der IP-Adresse und des Unix-Timestamp zusammengeführt:

$sid = md5($_SERVER['REMOTE_ADDR']) . md5(time());

Dadurch generiert sich ein Pseudohash, der die doppelte Länge der 32 Bytes eines einzelnen MD5-Hash aufweist (dies ist eigentlich löblich, geht jedoch auf Kosten des Avalanche-Effekts). Die Länge einer SID gibt in diesem Fall den Hinweis, dass es sich hierbei nicht mehr um einen üblichen MD5-Hashwert handeln kann.

Die gängigen Hash-Algorithmen generieren unabhängig von der Länge der Eingabe einen Hashwert mit stets gleicher Ausgabe. Bei MD5 sind dies 32 Bytes in der hexadezimalen Darstellung. Ein Hashwert der Länge 64 Bytes kann also offensichtlicherweise in zwei Hälften gebrochen werden, um die Teilhashes zu determinieren:

ef063394a388f4ab8ab196cec75cd90a8f9618d56ca6a9615eda2f05b9c9b54b
ef063394a388f4ab8ab196cec75cd90a 8f9618d56ca6a9615eda2f05b9c9b54b
Teil 1 (IP-Adresse) Teil 2 (Unix-Timestamp)

Ist dieser Schritt vollzogen, kann die übliche Analyse der jeweiligen Hashes erfolgen, wobei weitestgehend jeder für sich alleine untersucht werden kann. Abhängigkeiten zwischen den Hashes sind sehr selten beobachtet, da sie sowohl ein Verständnis für das Grundproblem der hier beschriebenen Techniken als auch ein breitflächiges Wissen zur Implementierung von sicheren Kryptosystemen erfordert.

In manchen Fällen wurde schon beobachtet, dass ein üblich generierter Hash-Wert manipuliert wurde, um dessen wahre Struktur und damit den zugrundeliegenden Algorithmus zu verschleiern. Zum Beispiel, indem dem Hashwert ein Zeichen entfernt oder hinzugefügt wurde. Beispiel einer solchen Implementierung, bei der sämtliche Nullen aus der hexadezimalen Rückgabe von md5() gelöscht oder am Ende ein zusätzliches Symbol angefügt werden:

$sid = str_replace("0", "", md5(time()));
$sid = md5(time()) . "a";

Zufällige Werte

Geht es um die Generierung von SIDs, dann ist der einzig richtige Weg, sich auf möglichst zufällige, nicht ableitbare und nicht beeinflussbare Ursprungswerte zu verlassen. Diese sollen (zu einem Grossteil) durch eine Zufallsfunktion wie rand() (in PHP gar am besten mit mt_rand()) generiert werden. Beispiel:

$sid = md5(rand(1000, 9999));

Doch auch hier pflegen Entwickler gerne Fehler zu machen bzw. Situationen falsch einzuschätzen. Als erstes muss bei per se nicht-eindeutigen Werten damit gerechnet werden, dass bei der legitimen Generierung aufgrund des Geburtstagsparadoxon eine Kollision provoziert wird. Vergebene SIDs sollten abgelegt und vor der Herausgabe einer neuen SID geprüft werden, ob diese schon belegt ist. Ist dies der Fall, muss eine andere SID – die wiederum vor der Herausgabe auf eine Belegung geprüft werden muss – erstellt werden. Dabei gilt sich ebenfalls auf den Sonderfall einzustellen, dass alle möglichen SIDs schon belegt sind:

Ein weiterer Fehler ist, dass die Entropie, wie sie durch Wertebereiche garantiert werden können, gerne überschätzt werden. Das genannte Beispiel generiert zufällige Zahlen im Bereich von 1’000 bis 9’999. Damit sind insgesamt 9’000 unterschiedliche Zahlen möglich (die Zahlen ≤0 bis 999 können laut Funktionsaufruf nicht generiert werden).

Ein Angreifer muss also lediglich 9’000 mögliche Grunddaten bzw. Hashwerte durchlaufen, um zu 100 % einen Treffer für eine bestehende SID zu erhalten. Dies ist weitaus weniger Aufwand, weder die aus 32 Byte bestehende Ausgabe des MD5-Hash suggerieren lassen. Als Grundwerte sollten also nicht nur Zahlen in einem Wertebereich, sondern ebenfalls Buchstaben und Sonderzeichen miteinbezogen werden. Ein Ausschliessen irgendwelcher Symbole oder Wertbereiche wirkt sich negativ auf die Verteilung der Daten aus.

Zusammenfassung

Die Generierung von SIDs ist in der modernen Programmierung relativ simpel und so wird dann auch gerne auf vorhersehbare Grunddaten zurückgegriffen. Jenachdem sind diese durch den Angreifer vorhersagbar oder gar manipulierbar. Ist dies der Fall, kann mit berechenbarem Aufwand eine legitime SID generiert und damit eine bestehende Sitzung kompromittiert werden.

Zufällige Werte, die durch Zufallsfunktionen wie rand() generiert werden, Identifikationsmechanismen auf anderer Ebene (z.B. Binden einer Session an eine IP-Adresse) und Ablauf einer Sitzung können dabei helfen, einen praktikablen Angriff erheblich zu erschweren oder gar unmöglich zu machen. Applikationsentwickler müssen sich den Risiken im Umgang mit Session-IDs bewusst sein und diese spezifisch adressieren.

About the Author

Marc Ruef

Marc Ruef has been working in information security since the late 1990s. He is well-known for his many publications and books. The last one called The Art of Penetration Testing is discussing security testing in detail. He is a lecturer at several faculties, like ETH, HWZ, HSLU and IKF. (ORCID 0000-0002-1328-6357)

Links

You need support in such a project?

Our experts will get in contact with you!

×
Specific Criticism of CVSS4

Specific Criticism of CVSS4

Marc Ruef

scip Cybersecurity Forecast

scip Cybersecurity Forecast

Marc Ruef

Voice Authentication

Voice Authentication

Marc Ruef

Bug Bounty

Bug Bounty

Marc Ruef

You want more?

Further articles available here

You need support in such a project?

Our experts will get in contact with you!

You want more?

Further articles available here