0%

Origins en cross-site verzoeken · eindproject

Eindproject: Project: verzoeken van ergens anders

Het verwijderformulier heeft nu twee eigen bewakers, een token en een controle op Origin, en één aanvalstest. Die test kan niet zeggen welke bewaker de vervalsing tegenhield. Haal een van beide weg, en hij slaagt nog steeds.

Dit project bewijst elke verdediging afzonderlijk. Bewezen heeft hier een eenvoudige betekenis: een test die nog steeds slaagt nadat je de aanvaller alles hebt gegeven wat de andere verdediging achterhoudt.

Zet een verdediging uit door die weg te geven

Er komt geen schakelaar in de server. Een test verandert wat de aanvaller in handen heeft.

  • Alleen het token. test_attack_token_alone_stops_the_neighbor voegt de buur vóór al het andere toe aan de vertrouwde set. De controle op Origin laat het vervalste formulier nu door. Het formulier draagt geen token, en het boek moet blijven.

  • Alleen de controle op Origin. test_attack_origin_check_alone_stops_the_neighbor leest het token van Ana uit de state, wat geen pagina kan, met state["sessions"][jar[0]["value"]]["csrf"]. Hij post het formulier met de body ("id=1&csrf=" + token).encode("utf-8"). De tokencontrole slaagt nu, en het boek moet blijven.

De eerste van de twee begint zo:

def test_attack_token_alone_stops_the_neighbor():
    """The Origin check is handed to the attacker: the neighbor is trusted. The token alone must stop the forgery."""
    state = reading_server.sample_state()
    state["trusted_origins"].add(steps.NOTES)

Beide tests posten vanaf steps.NOTES. Beide controleren dat de meeging voordat ze iets over het boek controleren, zoals je test uit les 5 doet.

Je test voegt https://notes.reading.example toe aan state["trusted_origins"] en post daarna het vervalste formulier zonder token. Wat bewijst het als hij slaagt?

Zie elk van deze twee tests één keer falen. Zet return True als eerste regel van csrf_matches, voer python -m pytest -q uit en haal de regel weer weg. Doe daarna hetzelfde in origin_allowed. Elke run eindigt met één fout, en dat is de test voor die verdediging.

Nog twee tests maken het beeld compleet. test_the_page_can_still_delete_its_own_book post het formulier met het token vanaf steps.OWN en controleert dat boek 1 weg is. Een verdediging die ook de eerlijke pagina tegenhoudt, heeft de service kapotgemaakt. In tests/test_browser.py staat test_samesite_alone_does_not_stop_the_neighbor naast je test uit les 5: het formulier van steps.NOTES draagt de cookie. Samen leggen de twee vast wat SameSite tegenhoudt en wat niet.

Eén plek voor de controle op Origin

Het verwijderformulier is niet de enige POST die door een cookie wordt geautoriseerd. Inloggen, uitloggen, de paginagrootte en een nieuw token lezen allemaal de sessiecookie. Het projectbestand somt de vijf op:

COOKIE_POSTS = [["login"], ["logout"], ["prefs"], ["tokens"], ["books", "delete"]]

Haal de controle uit de tak voor verwijderen en maak er de eerste opdracht van route() van, direct nadat segments is gezet. Een POST waarvan de segments in COOKIE_POSTS zitten en waarvan de Origin niet is toegestaan, krijgt meteen de 403, met een .

Waarom een , en geen regel zoals “elke POST behalve de boekroutes”? Het pad van het verwijderformulier heeft de vorm van het pad van een boek, dus een die op vorm is geschreven, laat dat formulier ongecontroleerd. En een regel door uitsluiting weigert POST /v0/books, die sinds with Python met 308 antwoordt en een token gebruikt, geen cookie. Een lijst heeft ook een prijs: die weet niets van een route die iemand later toevoegt. Het commentaar boven de lijst zegt wat je dan moet doen. Een nieuwe route die de sessiecookie leest, komt erin.

De controle op /login heeft een prijs, en die is een keuze: een commandline-client die geen Origin stuurt, kan niet meer inloggen.

Geen enkele GET verandert de state

test_attack_a_get_cannot_delete_a_book volgt een link vanaf een andere site. De van de link is steps.OWN + "/books/delete?id=1&csrf=" + token:

result = visit.visit(steps.OTHER, {"kind": "link", "url": url}, jar, send)

Een gevolgde link is een top-level GET, en een top-level GET, van een link of van een formulier dat met GET is verstuurd, is het enige soort cross-site verzoek dat een Lax-cookie draagt. Controleer dat de cookie meeging en dat het boek bleef. Je server antwoordt vandaag 405, omdat de route alleen POST toestaat, en deze test houdt dat zo.

Mijlpalen

  1. Vier tests in tests/test_attacks.py en één in tests/test_browser.py, waarvan je de eerste twee één keer hebt zien falen.

  2. De controle bovenaan route(). Je tests kunnen deze stap niet zien: ze slagen ervoor en erna. Check my work probeert de andere routes in COOKIE_POSTS voor je uit.

  3. Twee runs: de tests en het scenario. Het scenario is om te lezen, en wordt niet beoordeeld.

python -m pytest -q
python pl_sec.py cross-site-delete

De eerste run eindigt met 35 passed. In de tweede wordt het formulier van de buur nu geweigerd, en dat van other.example ook, voordat er een wordt opgezocht:

--- a page on https://notes.reading.example posts the same form ---
  cookie sent:         yes
  origin:              https://notes.reading.example
  the server answered: 403   (pl_sec sees this; the page does not)
  book ids left:       1, 2

De laatste regels zeggen:

--- result ---
0 of 2 forged forms deleted a book; the page's own form deleted its book
  1. Rij F12 van SECURITY.md, in je eigen woorden. De cel Test ervan noemt de vier aanvalstests van dit hoofdstuk. De status is open: the forgery half closed in 8.8, omdat de bevinding een tweede helft heeft.

Wat nog openstaat

Die tweede helft gaat over lezen. https://app.reading.example is een tweede front-end van de leeslijst. De pagina’s daarvan zouden mogen lezen wat je server antwoordt, en vandaag houdt de ze tegen zoals ze elke andere tegenhoudt. Hoofdstuk 9 geeft die ene origin toestemming om te lezen.

Opdracht

Bewijs elke verdediging afzonderlijk, zet de controle op Origin op één plek voor vijf routes en leg de bevinding vast.

Elke test logt ana eerst in via het model, zoals in les 5, en bereikt de code via de . Het token van haar is state["sessions"][jar[0]["value"]]["csrf"]. Het formulier is het verwijderformulier uit les 5, gepost met visit.visit(page, form, jar, send).

  1. Schrijf in tests/test_attacks.py, onder je test uit les 5, vier tests.

    • test_attack_token_alone_stops_the_neighbor(): eerst state["trusted_origins"].add(steps.NOTES). Post het formulier met de body b"id=1" vanaf steps.NOTES. Controleer dat "cookie" in de headers van het verzoek staat, en dat 1 in state["books"].

    • test_attack_origin_check_alone_stops_the_neighbor(): post het formulier met de body ("id=1&csrf=" + token).encode("utf-8") vanaf steps.NOTES, en laat state["trusted_origins"] met . Controleer dezelfde twee dingen.

    • test_attack_a_get_cannot_delete_a_book(): volg een link vanaf steps.OTHER, {"kind": "link", "url": steps.OWN + "/books/delete?id=1&csrf=" + token}. Controleer dezelfde twee dingen.

    • test_the_page_can_still_delete_its_own_book(): post het formulier met het token vanaf steps.OWN, en controleer dat 1 not in state["books"].

  2. Schrijf in tests/test_browser.py test_samesite_alone_does_not_stop_the_neighbor(): post het formulier met de body b"id=1" vanaf steps.NOTES en controleer dat "cookie" in de headers van het verzoek staat.

  3. Maak in route in reading_server.py dit de eerste opdracht nadat segments is gezet: als method == "POST", segments in COOKIE_POSTS en origin_allowed(request, state["trusted_origins"]) onwaar is, geef dan json_error(403, "this request did not come from a page of this service") terug. Haal de controle uit de tak "/books/delete".

  4. Voer python -m pytest -q uit tot het eindigt met 35 passed, en voer daarna python pl_sec.py cross-site-delete uit.

  5. Vul in SECURITY.md rij F12 in. De cel Test ervan noemt test_attack_another_origin_cannot_delete_a_book en de drie test_attack_-tests van deze les. De status is open: the forgery half closed in 8.8.

Klik daarna op Check my work. Die logt een eigen gebruiker in en bestuurt je server in het geheugen, met een eigen browsermodel. Elk van de vijf routes in COOKIE_POSTS krijgt een POST met een geldige waarvan de Origin ontbreekt, null is, een naburige host is of een andere site: elk moet een 403 krijgen en niets veranderen. Een POST en een PUT die door een token worden geautoriseerd, moeten nog steeds zonder Origin worden bediend, POST /v0/books moet nog steeds met 308 antwoorden, en POST /me, /about en / moeten nog steeds met 405 antwoorden. De vijf routes moeten nog steeds werken vanaf de eigen van de service. Vierentwintig GET- en HEAD-verzoeken mogen niets veranderen. De controles van les 6 en 7 draaien opnieuw met nieuwe . Je zeven tests van dit hoofdstuk worden op naam geselecteerd. Elk moet slagen tegen een eigen server van de controle die elke verdediging heeft, met een model waarvan de regels kloppen. Elk moet falen tegen de server, of met het model, die de ene fout heeft waarvoor de test is geschreven: geen tokencontrole, geen controle op Origin, geen van beide, een GET die verwijdert, het verwijderformulier dat vanaf elke pagina wordt geweigerd, een Lax-cookie bij een cross-site formulier-POST, en een cookie die bij de buur wordt achtergehouden. Geen enkele test mag een verbinding openen. Rij F12 wordt als tabelrij gelezen; de formulering wordt niet beoordeeld. Niets uit je wordt beoordeeld.