Origins en cross-site verzoeken · oefening
SameSite en de header Origin
Ga terug naar de uitvoer van python pl_sec.py cross-site-delete in les 5. Elk vervalst formulier had een regel die tot nu toe door niets is gebruikt:
origin: https://notes.reading.example
visit() voegt een header origin toe aan elk verzoek dat geen GET of HEAD is, zoals een browser doet. Het vervalste formulier zei dus al die tijd waar het vandaan kwam, en je server keek nooit.
Een header die de pagina niet kan zetten
De header is het lezen waard vanwege wie hem schrijft. De browser schrijft Origin, en het script van een pagina mag hem niet zetten. visit() houdt zich aan die regel: het weigert een wanted die het probeert. Bekijk het één keer. In deze regel doet een pagina op de buur zich voor als de leeslijst:
python -c "import visit; visit.visit('https://notes.reading.example', {'kind': 'fetch', 'method': 'POST', 'url': 'https://reading.example/logout', 'headers': {'Origin': 'https://reading.example'}}, [], print)"
De
ValueError: a page cannot set the Origin header; the browser writes it
Een programma dat geen browser is, kan elke Origin schrijven die het wil. Het heeft geen browser van Ana die haar
Exact, uit een set
sample_state() bevat sinds het begin van dit hoofdstuk een set: state["trusted_origins"], met één element, "https://reading.example". De controle stelt één vraag: of de in die set zit. Op een set teksten is in alleen waar voor een tekst die van het eerste tot het laatste teken gelijk is aan een element.
Elke lossere vergelijking laat iemand binnen. Hier zijn er vier, elk met een
| Een vergelijking die goed lijkt | Een origin die ze binnenlaat |
|---|---|
De waarde begint met https://reading.example | https://reading.example.other.example |
De waarde bevat reading.example | https://notreading.example |
| De waarde staat op dezelfde site als de leeslijst | https://notes.reading.example, de buur zelf |
De host is reading.example, welk schema ook | http://reading.example |
Een server accepteert een Origin die begint met https://reading.example. Welke origin komt binnen?
Een verzoek-origin_allowed(request, trusted) met request["headers"].get("origin", []). Hij geeft False terug, tenzij die lijst precies één waarde heeft. Daarna antwoordt hij of de waarde in trusted zit. Een verzoek met twee headers Origin wordt geweigerd, wat de eerste ook zegt.
Ontbrekend en null
Een browser stuurt in sommige gevallen Origin: null: vanaf een pagina die de opdracht kreeg geen referrer te sturen, vanuit een frame in een sandbox, en vanuit een bestand dat van schijf is geopend. Een oude of ongewone client stuurt helemaal geen Origin. Weiger beide. De "null" zit niet in de set, en een ontbrekende header is een lijst zonder waarden.
Deze weigering heeft een prijs, en die prijs is een keuze. Een hulpmiddel dat naar deze route post, moet de header sturen. pl_sec.py doet dat, en de tests in tests/test_attacks.py sturen hem sinds het begin van dit hoofdstuk. Het legt ook een later hoofdstuk vast. Als hoofdstuk 10 beveiligingsheaders toevoegt, mag het Referrer-Policy: no-referrer niet zetten, anders zou het eigen formulier van de leeslijst met Origin: null aankomen.
Het model schrijft altijd een echte origin. De null-gevallen komen uit de Fetch- en HTML-standaarden en worden hier niet getoond. Fetch voegt er nog twee toe: een verzoek dat via een andere origin werd doorverwezen, en een POST vanaf een https-pagina naar een http-adres. Een oudere header, Referer, draagt hetzelfde idee en kan op meer manieren ontbreken.
Een POST die door een cookie wordt geautoriseerd, komt aan met Origin: null. Wat doet de server?
Drie verdedigingen, twee aanvallers
Het verwijderformulier heeft nu drie verdedigingen. In dit hoofdstuk zijn twee aanvallers verschenen: een pagina op other.example en een pagina op de buur.
| Verdediging | Een formulier van other.example | Een formulier van notes.reading.example |
|---|---|---|
SameSite=Lax | tegengehouden: de cookie blijft thuis | niet tegengehouden: dezelfde site, dus de cookie gaat mee |
| Het token in het formulier | tegengehouden: het kan de pagina niet lezen | tegengehouden: het kan de pagina niet lezen |
De controle op Origin | tegengehouden: niet in de set | tegengehouden: niet in de set |
Houd alle drie, omdat elk op een andere manier faalt: een browser zonder SameSite, een bug in de pagina die het token laat uitlekken, een route die iemand vergeet. Een andere cursus van Python Land, Origin om een WebSocket-handshake te bewaken.
Welke verdediging houdt een vervalst formulier van notes.reading.example niet tegen?
Je opdracht
Schrijf
origin_allowed(request, trusted)inreading_server.py: precies één waarde voororigin, en die waarde in de set.Vraag het in de tak
"/books/delete"vanroute, bij een POST, vóór al het andere. Alsorigin_allowed(request, state["trusted_origins"])onwaar is, is het antwoordjson_error(403, "this request did not come from a page of this service"), en wordt deniet opgezocht.
Voer daarna de tests uit:
python -m pytest -q
De run eindigt nog steeds met 30 passed. Geen test faalt zonder de nieuwe controle, omdat het token het vervalste formulier al tegenhoudt. Dat is een gat in de tests, en het project in les 8 dicht het.
Klik daarna op Check my work.
Opdracht
Controleer de header Origin van het verwijderformulier, exact en tegen een set.
Schrijf in
reading_server.pyorigin_allowed(request, trusted). Leesrequest["headers"].get("origin", []), een. Geef Falseterug, tenzij de lijst precies één waarde heeft. Geef daarna terug of die waarde in de settrustedzit. Vergelijk de hele tekst en repareer niets: geen kleine letters, geen slash weggehaald, geen voorvoegsel.Roep in de tak
"/books/delete"vanroute, bij een POST,origin_allowed(request, state["trusted_origins"])aan voordat dewordt gelezen. Als die Falseteruggeeft, is het antwoordjson_error(403, "this request did not come from a page of this service"). Anders gaat de tak verder zoals eerst.
Voer python -m pytest -q uit. Het eindigt met 30 passed.
Klik daarna op Check my work. Die roept origin_allowed aan met verzoek-Origin, met null, met een lege waarde, met een tekst die maar een deel van een vertrouwde origin is, met een ander schema, met een naburige host van dezelfde site, met een vertrouwde host zonder zijn poort, met lookalikes die beginnen of eindigen zoals een vertrouwde origin, met hoofdletters, met een slash aan het einde en met een kale host. Daarna logt hij een eigen gebruiker in en bestuurt hij je server in het geheugen, met een eigen browsermodel. Een formulier van een naburige host dat het juiste token draagt, moet het boek laten staan en een 403 krijgen. Verzoeken met de juiste Origin, met null of met een lookalike moeten een 403 krijgen en niets veranderen. Een verzoek met een niet-vertrouwde Origin en zonder cookie moet een 403 krijgen en geen 401. Het eigen formulier van de pagina moet nog steeds verwijderen. Niets uit je