0%

Sessions, die halten · Abschlussprojekt

Abschlussprojekt: Projekt: Sessions, die halten

Drei Lektionen haben drei Reparaturen gebracht: eine neue ID bei der Anmeldung, zwei Limits und ein mit seinen Flags. Jede hat ihren eigenen Test. Dieses Projekt liest sie als eine Richtlinie, fügt das eine Stück hinzu, das noch fehlt, und beweist die ganze Richtlinie mit fünf Tests.

Die Richtlinie

FrageAntwort bei der Leseliste
Wann beginnt eine ?Wenn ein Besucher eine Einstellung speichert, und bei jeder Anmeldung, immer unter einer neuen ID aus secrets
Was enthält sie?user, page_size, created, last_seen
Was bewegt sich?last_seen, bei jeder Anfrage, die die Session aktiv vorfindet
Wann endet sie?Nach IDLE_LIMIT Sekunden ohne Anfrage, ABSOLUTE_LIMIT Sekunden nach ihrem Start, bei der Abmeldung und bei der Anmeldung, wenn eine neue Session sie ersetzt
Was sagt das Cookie?__Host-session, mit Secure, HttpOnly, SameSite=Lax, Path=/ und ohne Domain
Was darf ein Cache behalten?Nichts über eine Session: Jede Antwort, die ein Cookie setzt, und jede Session-Route sagt no-store

Jede Zeile bis auf eine ist fertig. Die Lücke steckt in „Wann endet sie?“.

Die Session, zu der niemand zurückkommt

current_session entfernt eine abgelaufene Session, wenn ihre ID benutzt wird. Ein Besucher, der nie zurückkommt, benutzt die ID nie, also bleibt der Eintrag in state["sessions"], solange der Server läuft. Und POST /prefs gibt jedem eine Session, der danach fragt.

sweep_sessions(state) schließt die Lücke. Es liest die Zeit aus state["clock"](), entfernt jede Session, für die session_expired(...) wahr ist, und gibt zurück, wie viele es entfernt hat. start_session ruft es als Erstes auf, also wird der Speicher aufgeräumt, wann immer eine Session beginnt.

Eine Einzelheit entscheidet, wie du es schreibst. Einen Eintrag aus einem zu entfernen, während eine for- dasselbe Dictionary liest, löst RuntimeError: dictionary changed size during iteration aus, sobald die Schleife ihren nächsten Schritt macht. Räum also in zwei Durchgängen auf: Sammle zuerst die abgelaufenen IDs in einer und entferne sie dann in einer zweiten Schleife über diese Liste.

Warum sammelt sweep_sessions die abgelaufenen IDs zuerst in einer Liste und entfernt sie in einer zweiten Schleife?

Meilensteine

1. Auch eine viel benutzte Session endet. Schreib test_attack_busy_session_still_ends(). Das ist der Dieb aus Lektion 3, der alle zehn Minuten eine Anfrage sendet. Melde dich unter einer festen Uhr an, die bei 1000 beginnt. Wiederhole dann: Addiere 600 zu clock["now"], frag GET /me und sichere zu, dass der Benutzer ana ist. Mach weiter, solange der nächste Schritt noch unter dem Limit bleibt:

    while clock["now"] + 600 < 1000 + reading_server.ABSOLUTE_LIMIT:

Nach der Schleife ist die Session 28200 Sekunden alt. Addiere 601, was sie eine Sekunde über das Limit bringt, frag noch einmal und sichere zu, dass der Benutzer None ist.

2. Die Abmeldung beendet die Session auf dem Server. Schreib test_attack_logout_ends_the_session_on_the_server(). Melde dich mit steps.login_cookie an, frag GET /me und sichere ana zu, sende POST /logout mit dem Cookie, frag dann GET /me mit demselben Cookie und sichere None zu. Der Angreifer ist jeder, der von vor der Abmeldung eine Kopie des Cookies behalten hat.

Führ python -m pytest -q aus. Es endet mit 13 passed: Beide neuen Tests laufen beim ersten Mal durch. Lektion 3 hat das absolute Limit schon gebaut, und with Python hat eine Abmeldung gebaut, die die Session entfernt. Diese zwei Tests beweisen keine Reparatur. Sie bewachen eine, und das Quiz unten sagt, wogegen.

Dein Abmeldetest läuft beim ersten Ausführen durch. Wofür ist er da?

3. Das Aufräumen. Schreib sweep_sessions(state) und ruf es als erste Zeile von start_session auf. Keiner deiner Tests wechselt die Farbe: python -m pytest -q endet weiterhin mit 13 passed.

4. Sieh der Richtlinie zu. Führ das Szenario für Sessions aus:

python pl_sec.py sessions

Es meldet sich an, lässt auf einer eigenen Uhr 31 Minuten vergehen, fragt noch einmal, meldet sich dann an und ab und probiert das alte Cookie. Die IDs sind hier gekürzt:

--- a session that is left alone ---
ana logs in                          POST /login   -> 200
set-cookie: __Host-session=<id>; HttpOnly; Max-Age=28800; Path=/; SameSite=Lax; Secure
ana asks who she is                  GET /me       -> 200  {"user": "ana", "page_size": 20}
(the clock moves 31 minutes)
the same cookie, 31 minutes later    GET /me       -> 200  {"user": null, "page_size": 20}
--- a session that is logged out ---
ana logs in again                    POST /login   -> 200
ana logs out                         POST /logout  -> 204
set-cookie: __Host-session=""; HttpOnly; Max-Age=0; Path=/; SameSite=Lax; Secure
the cookie from before the logout    GET /me       -> 200  {"user": null, "page_size": 20}
--- result ---
after 31 idle minutes the session answered as: nobody
after logout the old cookie answered as: nobody

5. Die Befunde. Prüfe in SECURITY.md, dass die Zeilen F3, F4 und F5 jeweils einen Test nennen und closed sagen.

Was noch offen ist

Das Aufräumen begrenzt, wie lange eine tote Session im Speicher bleibt. Es begrenzt nicht, wie viele aktive Besucher-Sessions ein Fremder in einer halben Stunde öffnen kann: Jedes POST /prefs ohne Cookie erzeugt eine, und keine davon läuft vor 30 Minuten ab. Ein echter Dienst fügt eine Obergrenze hinzu oder erzeugt eine Session erst, wenn er etwas hat, das es ist, gemerkt zu werden. Das Aufräumen liest außerdem bei jedem Start einer Session den ganzen Speicher, also macht dieselbe Flut jedes POST /prefs langsamer. Dieser Kurs nennt beide Probleme und baut keine der Behebungen.

Klicke dann auf Check my work. Deine Sessions halten jetzt. Die Bearer-Tokens nicht: Sie sind immer noch zwei feste im , die nie ablaufen, und Kapitel 5 ersetzt sie.

Aufgabe

Stell die -Richtlinie fertig.

  1. Schreib in tests/test_attacks.py test_attack_busy_session_still_ends() und test_attack_logout_ends_the_session_on_the_server(), wie die Lektion sie beschreibt. Beide verwenden steps.login_cookie(state, "ana", "correct horse"). Der Test mit der viel benutzten Session läuft unter helpers.fixed_clock(...), fragt alle 600 Sekunden GET /me und sichert jedes Mal ana zu, solange der nächste Schritt noch unter reading_server.ABSOLUTE_LIMIT nach der Anmeldung bleibt, bewegt die Uhr dann auf eine Sekunde nach diesem Limit und sichert None zu. Der Abmeldetest sichert vor der Abmeldung ana und danach None zu, mit demselben .

  2. Schreib in reading_server.py sweep_sessions(state): Lies die Zeit aus state["clock"](), entferne jede Session, für die session_expired(...) wahr ist, und gib zurück, wie viele du entfernt hast.

  3. Ruf sweep_sessions(state) als erste Zeile von start_session auf.

  4. Führ python -m pytest -q (13 Tests laufen durch) und python pl_sec.py sessions aus.

  5. Stell in SECURITY.md sicher, dass die Zeilen F3, F4 und F5 jeweils einen Test nennen und closed sagen.

Klicke dann auf Check my work. Es wiederholt die Prüfungen der Lektionen 2, 3 und 4 mit anderen Uhrzeiten, IDs und Seitengrößen: zuerst die zwei Limits und die Uhr, dann Name und Flags des Cookies, dann die neue ID bei der Anmeldung und no-store. Es ruft sweep_sessions auf einem Speicher mit einer aktiven, einer im Leerlauf befindlichen und einer zu alten Session auf, was 2 zurückgeben und die aktive behalten muss, und ruft start_session auf einem solchen Speicher auf, was ihn aufräumen muss; ein Aufräumen, das ein neues aus den aktiven Sessions baut, wird akzeptiert. Es schickt eine Anmeldung als Bytes durch http_adapter, ohne einen zu öffnen, und liest die Zeilen set-cookie und cache-control, die zurückkommen. Es führt deine fünf Tests dieses Kapitels über ihre Namen gegen Server aus, die es an die Stelle von reading_server.handle und reading_server.sample_state setzt: Gegen einen korrekten Server müssen alle fünf durchlaufen; jeder muss gegen genau den Fehler fehlschlagen, für den er da ist (eine Anmeldung, die die ID behält, kein Leerlauflimit, kein absolutes Limit, eine Abmeldung, die die Session behält, ein Cookie ohne Secure); und alle fünf müssen gegen einen Server fehlschlagen, der jede Anmeldung ablehnt. Zuletzt liest es die Zeilen F3, F4 und F5 von SECURITY.md. Nichts aus deinem wird bewertet.