Kapitel 4
Sessions, die halten
Eine Session-ID ist ein Schlüssel
Nach einer Anmeldung wird das Passwort nicht noch einmal gesendet. Der Browser sendet ein secrets.token_urlsafe(16) erzeugt, das sind 128 zufällige Bits, also errät niemand eine. In diesem Kapitel geht es um alle anderen Wege, an eine funktionierende Session-ID zu kommen.
Was im Projekt dieses Kapitels neu ist
Das Projekt ist dein Server vom Ende von Kapitel 3 mit einer zusätzlichen
POST /prefs merkt sich eine Seitengröße für einen Leser, der sich nicht angemeldet hat. Um sich irgendetwas zu merken, braucht der Server eine Session, also gibt diese Route schon vor der Anmeldung eine aus: eine Besucher-Session, deren user None ist.
elif segments == ["prefs"]:
if method == "POST":
response = prefs_error(request)
if response is None:
page_size = json_body(request)["page_size"]
response = json_response(200, {"page_size": page_size})
session = current_session(request, state)
if session is None:
session_id = start_session(state, None, page_size=page_size)
response["headers"]["set-cookie"] = session_cookie(session_id)
else:
session["page_size"] = page_size
Eine Session ist jetzt {"user": ..., "page_size": ...}, und GET /me meldet beides: {"user": null, "page_size": 5} für einen Besucher, der 5 gewählt hat. Mit der Funktion kamen zwei Hilfsfunktionen: session_id_from(request) liest die ID aus dem Cookie, und current_session(request, state) gibt die Session zurück, die diese ID nennt. sample_state() hat außerdem einen Eintrag "clock" für Lektion 3, und drei Funktionen sind Platzhalter, die du in den Lektionen 2, 3 und 5 schreibst.
Der Anmeldezweig musste sich ebenfalls ändern, weil ein Besucher, der sich anmeldet, schon eine Session hat. Nach einem korrekten Passwort führt er jetzt diese vier Zeilen aus:
session_id = session_id_from(request)
if session_id is None or session_id not in state["sessions"]:
session_id = start_session(state, None)
state["sessions"][session_id]["user"] = user
Wenn die Anfrage ein Cookie mitbringt, das eine Session nennt, behält die Anmeldung diese Session und schreibt den Benutzer hinein. So überlebt die Seitengröße die Anmeldung.
Lies die vier Zeilen noch einmal und frag dich, wer die Session-ID gewählt hat. Der Server hat sie in diesem Moment nicht gewählt. Die ID kam mit der Anfrage, und der Server hat nur geprüft, dass er die ID kennt. Einer ID, die an einen Besucher ausgegeben wurde, wird dann ein angemeldeter Benutzer anvertraut.
Welche der vier vorgegebenen Zeilen im Anmeldezweig gibt einer ID, die der Server nicht gewählt hat, einen angemeldeten Benutzer?
Der Angriff Schritt für Schritt
Drei Parteien sind beteiligt. Die ID ist auf K7f...Qw gekürzt.
attacker victim's browser server
| | |
1 |-- POST /prefs --------------------------->| new session K7f...Qw:
|<-- set-cookie: session=K7f...Qw ----------| user None, page_size 5
2 |-- plants K7f...Qw --->| |
3 | |-- POST /login --->|
| | cookie K7f...Qw |
| | ana's password |
4 | | | its user is now "ana"
5 |-- GET /me, cookie: session=K7f...Qw ----->|
|<-- {"user": "ana", "page_size": 5} -------|
Der Angreifer sendet
POST /prefsund bekommt eine Session-ID.Der Angreifer bringt diese ID in den Browser des Opfers. Der nächste Abschnitt sagt, wie.
Das Opfer meldet sich mit seinem eigenen Passwort an. Sein Browser sendet das untergeschobene Cookie mit.
Der Server findet die Session, die das Cookie nennt, und schreibt
anahinein.Der Angreifer sendet
GET /memit der ID, die er seit Schritt 1 hat, und der Server antwortet als ana.
Dieselbe ID ist in den Schritten 1, 3 und 5 unterwegs. Der Angreifer hat nie eine ID erraten und nie eine gestohlen. Der Server hat einen
Dieser Block spielt den Angriff an einem einfachen
Er gibt the victim's cookie now holds: id-the-attacker-knows und the attacker asks who that is: ana aus.
Was gibt das aus?
sessions = {"abc": {"user": None}}
sessions["abc"]["user"] = "ana"
print(len(sessions), sessions["abc"]["user"])
Wie kommt die ID in ihren Browser?
Für Schritt 2 gibt es zwei verbreitete Wege.
Ein benachbarter Host. Angenommen, die Leseliste liegt unter
reading.exampleund eine Notiz-App unternotes.reading.example. Eine Seite auf dem Notiz-Host kann ein Cookie für die ganze Domainreading.examplesetzen, und ein Browser sendet dieses Cookie dann auch an die Leseliste. Lektion 4 schließt diesen Weg im Cookie selbst.Ein gemeinsam genutzter Computer. Der Angreifer besucht die Leseliste zuerst, notiert sich die ID und lässt das Cookie zurück. Die nächste Person an diesem Computer meldet sich damit an.
Nichts auf dieser Website ist ein Browser, also beschreibt der Kurs Schritt 2 und zeigt ihn nicht: Ein echter Browser wäre das, was das untergeschobene Cookie trägt. Jeder andere Schritt läuft in der nächsten Lektion gegen deinen Server.
Ein Server, der die Session-ID auch aus der
Vier Wege, eine Session-ID zu verlieren
| Wie die ID verloren geht | Was das bedeutet | Wo es behandelt wird |
|---|---|---|
| Erraten | Der Angreifer probiert IDs aus, bis eine funktioniert | In HTTP with Python durch eine ID aus secrets geschlossen |
| Festgelegt | Der Angreifer kennt die ID, bevor sie etwas wert ist | Dieser Angriff; Lektion 2 |
| Unterwegs oder aus einer Seite gelesen | Eine Kopie entsteht, während das Cookie ohne HTTPS ein Netzwerk durchquert, oder durch ein Skript in einer Seite | Lektion 4 |
| Nie in den Ruhestand geschickt | Eine Kopie funktioniert weiter, weil der Server die Session nie beendet | Befund F3; Lektion 3 |
Eine Session-ID funktioniert noch ein Jahr, nachdem ihr Benutzer sie zuletzt benutzt hat. Welcher der vier Verluste ist das?
Die Reparatur in einem Satz
Wenn sich ändert, was einer Session anvertraut ist, gib ihr eine neue ID. Eine Anmeldung ist so eine Änderung, und die alte ID, die jemand anderes kennen könnte, darf nichts mehr nennen.
Es gibt the victim's new id is 22 characters the attacker never saw und the attacker asks who the old id is: None aus. Die Seitengröße ist in die neue Session umgezogen, und die alte ID nennt nichts.
Der Angreifer hat weder anas Passwort noch das Cookie gesehen, das ihre Anmeldung zurückgegeben hat. Was wusste er?
In dieser Lektion hat sich an deinem Server nichts geändert. Die nächste Lektion führt den Angriff gegen ihn aus und schließt ihn dort.