0%

Sessies die standhouden · eindproject

Eindproject: Project: sessies die standhouden

Drie lessen deden drie reparaties: een nieuwe id bij de login, twee limieten, en een met zijn vlaggen. Elk heeft een eigen test. Dit project leest ze als één beleid, voegt het ene stuk toe dat nog ontbreekt, en bewijst het hele beleid met vijf tests.

Het beleid

VraagAntwoord op de leeslijst
Wanneer begint een ?Als een bezoeker een voorkeur opslaat, en bij elke login, altijd onder een nieuwe id uit secrets
Wat bevat ze?user, page_size, created, last_seen
Wat verschuift er?last_seen, bij elk verzoek dat de sessie levend aantreft
Wanneer eindigt ze?Na IDLE_LIMIT seconden zonder verzoek, ABSOLUTE_LIMIT seconden nadat ze begon, bij het uitloggen, en bij de login, als een nieuwe sessie haar vervangt
Wat zegt de cookie?__Host-session, met Secure, HttpOnly, SameSite=Lax, Path=/ en geen Domain
Wat mag een cache bewaren?Niets over een sessie: elk antwoord dat een cookie zet, en elke sessieroute, zegt no-store

Elke rij op één na is af. Het gat zit in “Wanneer eindigt ze?”.

De sessie waar niemand naar terugkomt

current_session verwijdert een verlopen sessie als haar id gebruikt wordt. Een bezoeker die nooit terugkomt, gebruikt de id nooit, dus de vermelding blijft in state["sessions"] staan zo lang als de server draait. En POST /prefs geeft een sessie aan iedereen die erom vraagt.

sweep_sessions(state) dicht het gat. Hij leest de tijd uit state["clock"](), verwijdert elke sessie waarvoor session_expired(...) waar is, en geeft terug hoeveel hij er verwijderde. start_session roept hem als eerste aan, zodat de opslag wordt opgeruimd telkens als er een sessie begint.

Eén detail bepaalt hoe je hem schrijft. Een vermelding uit een verwijderen terwijl een for- dezelfde dict leest, geeft RuntimeError: dictionary changed size during iteration zodra de lus zijn volgende stap zet. Ruim dus in twee rondes op: verzamel eerst de verlopen id’s in een , en verwijder ze daarna in een tweede lus over die lijst.

Waarom verzamelt sweep_sessions eerst de verlopen id's in een lijst en verwijdert hij ze in een tweede lus?

Mijlpalen

1. Een drukke sessie eindigt toch. Schrijf test_attack_busy_session_still_ends(). Het is de dief uit les 3, die elke tien minuten een verzoek stuurt. Log in onder een vaste klok die bij 1000 begint. Herhaal daarna: tel 600 op bij clock["now"], vraag GET /me, en controleer dat de gebruiker ana is. Ga door zolang de volgende stap nog onder de limiet blijft:

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

Na de lus is de sessie 28200 seconden oud. Tel er 601 bij op, waarmee ze één seconde voorbij de limiet komt, vraag opnieuw, en controleer dat de gebruiker None is.

2. Uitloggen beëindigt de sessie op de server. Schrijf test_attack_logout_ends_the_session_on_the_server(). Log in met steps.login_cookie, vraag GET /me en controleer ana, stuur POST /logout met de cookie, vraag daarna GET /me met dezelfde cookie en controleer None. De aanvaller is iedereen die een kopie van de cookie van vóór het uitloggen bewaarde.

Voer python -m pytest -q uit. Het eindigt met 13 passed: beide nieuwe tests slagen de eerste keer. Les 3 bouwde de absolute limiet al, en with Python bouwde een uitlog die de sessie verwijdert. Deze twee tests bewijzen geen reparatie. Ze bewaken er een, en de quiz hieronder zegt waartegen.

Je uitlogtest slaagt de eerste keer dat je hem uitvoert. Waar dient hij voor?

3. Het opruimen. Schrijf sweep_sessions(state) en roep hem aan als eerste regel van start_session. Geen enkele test van jou verandert van kleur: python -m pytest -q eindigt nog steeds met 13 passed.

4. Bekijk het beleid. Voer het sessiescenario uit:

python pl_sec.py sessions

Het logt in, laat 31 minuten verstrijken op een eigen klok, vraagt opnieuw, logt daarna in en uit en probeert de oude cookie. De id’s zijn hier ingekort:

--- 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. De bevindingen. Controleer in SECURITY.md dat de rijen F3, F4 en F5 elk een test noemen en closed zeggen.

Wat nog open staat

Opruimen begrenst hoe lang een dode sessie in de opslag blijft. Het begrenst niet hoeveel levende bezoekerssessies een vreemde in een half uur kan openen: elke POST /prefs zonder cookie maakt er een, en geen ervan verloopt binnen 30 minuten. Een echte dienst voegt een maximum toe, of wacht met een sessie maken tot hij iets heeft wat het onthouden waard is. Het opruimen leest ook elke keer dat er een sessie begint de hele opslag, dus dezelfde vloed maakt elke POST /prefs trager. Deze cursus noemt beide problemen en bouwt geen van beide oplossingen.

Klik daarna op Check my work. Je sessies houden nu stand. De bearer-tokens niet: dat zijn nog steeds twee vaste in de die nooit verlopen, en hoofdstuk 5 vervangt ze.

Opdracht

Maak het sessiebeleid af.

  1. Schrijf in tests/test_attacks.py test_attack_busy_session_still_ends() en test_attack_logout_ends_the_session_on_the_server() zoals de les beschrijft. Beide gebruiken steps.login_cookie(state, "ana", "correct horse"). De drukke test draait onder helpers.fixed_clock(...), vraagt elke 600 seconden GET /me en controleert elke keer ana zolang de volgende stap nog onder reading_server.ABSOLUTE_LIMIT na de login blijft, zet daarna de klok op één seconde voorbij die limiet en controleert None. De uitlogtest controleert ana vóór het uitloggen en None erna, met dezelfde .

  2. Schrijf in reading_server.py sweep_sessions(state): lees de tijd uit state["clock"](), verwijder elke waarvoor session_expired(...) waar is, en geef terug hoeveel je er verwijderde.

  3. Roep sweep_sessions(state) aan als eerste regel van start_session.

  4. Voer python -m pytest -q (13 tests slagen) en python pl_sec.py sessions uit.

  5. Zorg in SECURITY.md dat de rijen F3, F4 en F5 elk een test noemen en closed zeggen.

Klik daarna op Check my work. Die herhaalt de controles van les 2, 3 en 4 met andere kloktijden, id’s en paginagroottes: eerst de twee limieten en de klok, daarna de naam en de vlaggen van de cookie, daarna de nieuwe id bij de login en no-store. Hij roept sweep_sessions aan op een opslag met één levende, één inactieve en één te oude sessie, wat 2 moet teruggeven en de levende moet houden, en roept start_session aan op zo’n opslag, die hem moet opruimen; een opruiming die een nieuwe van de levende sessies bouwt, wordt geaccepteerd. Hij stuurt een login als bytes door http_adapter, zonder een te openen, en leest de regels set-cookie en cache-control die terugkomen. Hij voert je vijf tests van dit hoofdstuk op naam uit tegen servers die hij op de plaats van reading_server.handle en reading_server.sample_state zet: alle vijf moeten slagen tegen een correcte server; elk moet falen tegen de ene fout waarvoor hij bedoeld is (een login die de id houdt, geen inactiviteitslimiet, geen absolute limiet, een uitlog die de sessie houdt, een cookie zonder Secure); en alle vijf moeten falen tegen een server die elke login weigert. Als laatste leest hij de rijen F3, F4 en F5 van SECURITY.md. Niets uit je wordt beoordeeld.