0%

Eindproject: maak de leeslijst veilig · eindproject

Eindproject: Twee sites op loopback

Het bewijs is klaar. Elf tests slagen, de zes van het model slagen, en elk daarvan draaide in het geheugen: er ging een verzoek- in handle() en er kwam een antwoord-dict uit. Deze les voegt geen bewijs toe. Ze voegt een demonstratie toe: dezelfde service, achter de echte adapter, die echte bytes op een beantwoordt, met een tweede site ernaast.

Dat is het waard om één keer te zien, want het geheugen slaat dingen over. In het geheugen is er geen Content-Length om fout te doen, geen preflight die als eigen verzoek reist, geen tweede server en geen serve() die moet stoppen.

De reeks

python pl_sec.py two-sites start twee servers op 127.0.0.1. De ene is je leeslijst, via je make_server() en serve(). De andere is een kleine tweede site die de eigen pagina van de frontend serveert. Daarna speelt het drie soorten pagina’s na via je visit.visit() en je mini_browser.py: de frontend op https://app.reading.example, de buur op https://notes.reading.example, en een pagina op https://other.example. De namen bestaan alleen in het model. Elke byte gaat naar deze computer.

Ana is vanaf stap 0 ingelogd op https://reading.example, en de cookiejar van het model bevat haar . Dek de laatste drie kolommen af voordat je iets uitvoert, en voorspel ze.

StapDe pagina staat opZe vraagt omDe server antwoordtCookie verstuurdDe pagina
0reading.exampleinloggen (een fetch met Basic-credentials)200neeleest het; de cookie wordt opgeslagen
1app.reading.examplehaar eigen pagina, van de tweede site200neeleest het
2app.reading.examplePOST /tokens met credentials200jaleest het token
3app.reading.exampleGET /books met het token, na een preflight200neeleest de
4app.reading.examplePUT /books/1 met het token, na een preflight200neeleest het boek
5app.reading.exampleGET /me met credentials200jaleest ana
6notes.reading.examplehet verwijderformulier, met een geraden token403jakan het antwoord op een formulier niet lezen
7notes.reading.examplePOST /tokens met credentials403jamag het niet lezen
8notes.reading.exampleGET /me met credentials200jamag het niet lezen
9other.examplehet verwijderformulier403neekan het antwoord op een formulier niet lezen
10other.exampleGET /about200neemag het niet lezen
11other.exampleGET /books met een tokennooit verstuurd; de preflight werd geweigerdneegeblokkeerd
12geen: een client die geen browser isGET /about met Origin: https://other.example200needrukt de body af

De statuskolom is wat pl_sec.py ziet op de plek waar het elk verzoek naar de server brengt. Bij een formulier ziet een pagina die status nooit, en visit() geeft alleen terug wat de pagina mag lezen.

Stap 6 en 9 krijgen dezelfde status om verschillende redenen, en in de cookiekolom zie je het verschil. In stap 9 hield SameSite=Lax de cookie thuis, en weigerde de Origin-controle het verzoek ook nog. In stap 6 ging de cookie mee, omdat de buur op dezelfde site zit, en weigerde de Origin-controle het formulier voordat er naar het token werd gekeken.

In stap 8 antwoordt de server 200 met Ana's naam, op een verzoek dat een pagina op notes.reading.example veroorzaakte. Is dat een gat?

Voer het uit

python pl_sec.py two-sites

Met elke bevinding gedicht en het model hersteld, ziet de run er zo uit:

pl_sec: two-sites, your server and a second site on 127.0.0.1 (a port the system chose)
The names exist only in the model. Every byte goes to this computer, over plain HTTP.
--- ana logs in at the reading list ---
step  0  reading.example        log in                                 -> 200        cookie: no   reads the answer
--- the front end ---
step  1  app.reading.example    its own page, from the second site     -> 200        cookie: no   reads the answer
step  2  app.reading.example    POST /tokens with credentials          -> 200        cookie: yes  reads the answer
step  3  app.reading.example    GET /books with the token              -> 200        cookie: no   reads the answer
step  4  app.reading.example    PUT /books/1 with the token            -> 200        cookie: no   reads the answer
step  5  app.reading.example    GET /me with credentials               -> 200        cookie: yes  reads the answer
--- the neighbor on the same site ---
step  6  notes.reading.example  the delete form, with a guessed token  -> 403        cookie: yes  does not read the answer
step  7  notes.reading.example  POST /tokens with credentials          -> 403        cookie: yes  does not read the answer
step  8  notes.reading.example  GET /me with credentials               -> 200        cookie: yes  does not read the answer
--- a page on another site ---
step  9  other.example          the delete form                        -> 403        cookie: no   does not read the answer
step 10  other.example          GET /about                             -> 200        cookie: no   does not read the answer
step 11  other.example          GET /books with a token                -> never sent cookie: no   does not read the answer
--- a client that is not a browser ---
step 12  GET /about with Origin: https://other.example  -> 200  it prints the body: {"service": "reading list", "version": 1}
--- result ---
book 1 is still in the list, with status 'done'
all 13 steps match the contract in SECURITY.md
serve() returned after 16 connections; the server is closed
pl_sec: every thread has stopped

De leeslijst handelde veertien verzoeken af: dertien van het model en het ene van de gewone client. pl_sec.py geeft je server zestien verbindingen, opent en sluit de verbindingen die niemand gebruikte, en wacht daarna tot serve() terugkeert. Daarom kunnen de laatste regels zeggen dat de server gesloten is.

Als een stap afwijkt van het contract, noemt het resultaatblok de stap en de kolom. Herstel niets alleen op basis van de uitvoer. Zoek de test die die stap dekt, in tests/test_attacks.py of tests/test_browser.py, laat hem falen, en werk vanaf daar, zoals in de vorige twee lessen. Een verschil in de cookiekolom ligt aan het model. Een verschil in de status ligt aan de server. Een verschil in wat de pagina mag lezen, kan aan allebei liggen.

Wat de run niet kan laten zien

Hij kan bevinding 10 niet laten zien. In stap 6 draagt het formulier van de buur een geraden token, en de Origin-controle weigert het voordat er naar het token wordt gekeken. De stap zou er precies hetzelfde uitzien als bevinding 10 open was. Alleen je test bewijst die bevinding, door een vertrouwde Origin te sturen met het token van een andere .

En hij laat geen browser zien. Er deed in deze cursus op de site geen echte browser mee. De drie “pagina’s” zijn beschrijvingen van verzoeken die worden doorgegeven aan een model dat jij schreef en dat je in dit hoofdstuk moest herstellen, en het model volgt de standaarden alleen zover zijn regels reiken.

Stap 12 krijgt de body die stap 10 niet mocht lezen. Wat zegt dat over CORS?

Afronden

Voer alles nog één keer uit:

python -m pytest -q

Zeventien tests slagen. SECURITY.md is net zo bevroren als het was, en de stand ervan is nu deze: elf bevindingen gedicht, elk met een test die je schreef zonder dat iemand je vertelde waar je moest kijken, en F14 bewust open. Een ondertekend token dat uitlekt, werkt nog steeds tot het verloopt, hooguit vijftien minuten, en om dat te dichten zou je een opgeslagen lijst van ingetrokken tokens nodig hebben, en die heeft deze service niet.

Druk daarna op Check my work. Die kan je niet zien, dus de loopbackrun is alleen van jou. Hij voert dezelfde dertien stappen uit in het geheugen, met jouw server en jouw model, en vergelijkt elke rij met het contract; als een rij afwijkt, zet hij een correct model en daarna een correcte server in om je te vertellen aan welke kant je moet kijken. Hij voert je elf tests uit tegen je bestanden, en daarna elk ervan nog een keer tegen een server die de bijbehorende bevinding heeft, om te zien dat geen test onderweg naar groen is versoepeld. Hij controleert met een vervanger die niets opent dat serve() stopt na de verbindingen die het kreeg en zijn server sluit. En hij voert de aanvalstests van de eerdere hoofdstukken nog één keer uit.

Opdracht

Draai de afgemaakte service en een tweede site op het , en herstel alles wat de run laat zien.

  • Voer python pl_sec.py two-sites uit in de . Het resultaatblok moet zeggen dat alle 13 stappen overeenkomen met het contract in SECURITY.md, dat serve() is teruggekeerd en dat elke thread is gestopt.

  • Als een stap afwijkt, zoek dan de test in tests/test_attacks.py of tests/test_browser.py die hem dekt, laat die falen, en herstel de code vanaf daar. Versoepel geen test om hem groen te krijgen.

  • Voer python -m pytest -q uit. Alle zeventien tests slagen.

Als les 2 en 3 af zijn, heeft deze les geen nieuwe code nodig.

Druk daarna op Check my work. Die kan je terminal niet zien. Hij voert dezelfde dertien stappen uit in het geheugen via visit.visit(), je mini_browser.py en je handle(), en vergelijkt de status, de en wat de pagina mag lezen met het contract; als een rij afwijkt, probeert hij om de beurt een correct model en een correcte server om te zeggen aan welke kant je moet kijken. Hij voert je elf tests uit tegen je bestanden, en elk ervan opnieuw tegen een server die de bijbehorende bevinding heeft, waar hij nog steeds moet falen. Hij voert tests/test_browser.py uit, de rijen van “Must still work”, en zijn eigen kopie van de aanvalstests uit hoofdstuk 1 tot en met 10. Hij roept serve() aan met een vervangende server die niets opent en handle_request()-aanroepen telt, en make_server() met http.server vervangen, zodat er geen wordt gebonden.