Hoofdstuk 4
Sessies die standhouden
Een sessie-id is een sleutel
Na een login wordt het wachtwoord niet opnieuw gestuurd. De browser stuurt een secrets.token_urlsafe(16), dat is 128 willekeurige bits, dus niemand raadt er een. Dit hoofdstuk gaat over alle andere manieren om een werkende sessie-id in handen te krijgen.
Wat er nieuw is in het project van dit hoofdstuk
Het project is je server van het eind van hoofdstuk 3 met één toegevoegde
POST /prefs onthoudt een paginagrootte voor een lezer die niet is ingelogd. Om iets te onthouden heeft de server een sessie nodig, dus deze route deelt er al vóór de login een uit: een bezoekerssessie, waarvan user None is.
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
Een sessie is nu {"user": ..., "page_size": ...}, en GET /me meldt beide: {"user": null, "page_size": 5} voor een bezoeker die 5 koos. Met de functie kwamen twee hulpfuncties mee: session_id_from(request) leest de id uit de cookie, en current_session(request, state) geeft de sessie terug die die id aanduidt. sample_state() heeft ook een vermelding "clock" voor les 3, en drie functies zijn stubs die je in les 2, 3 en 5 schrijft.
De logintak moest ook veranderen, omdat een bezoeker die inlogt al een sessie heeft. Na een juist wachtwoord voert hij nu deze vier regels uit:
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
Als het verzoek een cookie meebrengt die een sessie aanduidt, houdt de login die sessie en schrijft hij de gebruiker erin. Zo overleeft de paginagrootte de login.
Lees de vier regels nog eens en vraag je af wie de sessie-id koos. De server koos hem niet op dit moment. De id kwam binnen in het verzoek, en de server controleerde alleen dat hij de id kent. Een id die aan een bezoeker was uitgedeeld, wordt dan vertrouwd met een ingelogde gebruiker.
Welke van de vier meegeleverde regels in de logintak geeft een ingelogde gebruiker aan een id die de server niet koos?
De aanval, stap voor stap
Er doen drie partijen mee. De id is ingekort tot K7f...Qw.
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} -------|
De aanvaller stuurt
POST /prefsen krijgt een sessie-id.De aanvaller krijgt die id in de browser van het slachtoffer. Het volgende deel zegt hoe.
Het slachtoffer logt in met haar eigen wachtwoord. Haar browser stuurt de geplante cookie mee.
De server vindt de sessie die de cookie noemt en schrijft
anaerin.De aanvaller stuurt
GET /memet de id die hij sinds stap 1 heeft, en de server antwoordt als ana.
Dezelfde id reist mee in stap 1, 3 en 5. De aanvaller raadde nooit een id en stal er nooit een. De server promoveerde een
Dit blok speelt de aanval na op een gewone
Het drukt the victim's cookie now holds: id-the-attacker-knows en the attacker asks who that is: ana af.
Wat drukt dit af?
sessions = {"abc": {"user": None}}
sessions["abc"]["user"] = "ana"
print(len(sessions), sessions["abc"]["user"])
Hoe komt de id in haar browser?
Stap 2 heeft twee gebruikelijke routes.
Een naburige host. Stel dat de leeslijst op
reading.examplestaat en een notitie-app opnotes.reading.example. Een pagina op de notitiehost kan een cookie zetten voor het hele domeinreading.example, en een browser stuurt die cookie dan ook naar de leeslijst. Les 4 sluit deze route in de cookie zelf.Een gedeelde computer. De aanvaller bezoekt eerst de leeslijst, noteert de id, en laat de cookie achter. De volgende persoon aan die computer logt ermee in.
Niets op deze site is een browser, dus de cursus beschrijft stap 2 en laat hem niet zien: een echte browser is wat de geplante cookie zou meedragen. Elke andere stap draait in de volgende les tegen je server.
Een server die de sessie-id ook uit de
Vier manieren om een sessie-id kwijt te raken
| Hoe de id verloren gaat | Wat het betekent | Waar het wordt afgehandeld |
|---|---|---|
| Geraden | De aanvaller probeert id’s tot er een werkt | Gesloten in HTTP with Python door een id uit secrets |
| Vastgelegd | De aanvaller kent de id voordat hij iets waard is | Deze aanval; les 2 |
| Onderweg of uit een pagina gelezen | Er wordt een kopie gemaakt terwijl de cookie een netwerk zonder HTTPS oversteekt, of door een script in een pagina | Les 4 |
| Nooit ingetrokken | Een kopie blijft werken omdat de server de sessie nooit beëindigt | Bevinding F3; les 3 |
Een sessie-id werkt nog een jaar nadat zijn gebruiker hem voor het laatst gebruikte. Welke van de vier manieren is dat?
De reparatie, in één zin
Als verandert waarmee een sessie vertrouwd wordt, geef haar dan een nieuwe id. Een login is zo’n verandering, en de oude id, die iemand anders misschien kent, moet niets meer aanduiden.
Het drukt the victim's new id is 22 characters the attacker never saw en the attacker asks who the old id is: None af. De paginagrootte is naar de nieuwe sessie verhuisd, en de oude id duidt niets aan.
De aanvaller heeft het wachtwoord van ana nooit gezien, en ook niet de cookie die haar login teruggaf. Wat wist hij wel?
Er is in deze les niets aan je server veranderd. De volgende les voert de aanval erop uit en sluit hem daar.