Invoer die code wordt · eindproject
Eindproject: Project: niets gaat ongecontroleerd over de grens
Dit hoofdstuk heeft één regel in twee helften. Controleer een body op de heenweg, met een allowlist en limieten. Bewaak elke plek waar tekst naar buiten gaat, naar iets dat wordt geparset: een header, een <b>Dune</b> is een geldige titel, en moet in de pagina toch ge-escapet worden.
De validator en de vier bewakingen zijn geschreven. Twee van de vijf hebben een aanvalstest. Dit project schrijft de andere drie tests, voegt één bewaking toe op alles wat handle() verlaat, en sluit twee bevindingen.
Nog drie aanvalstests
Elke test voert een aanval uit en controleert dat die wordt geweigerd, zoals in les 1 en 4. Elke test controleert ook dat een eerlijk verzoek werkt, zodat hij niet kan slagen tegen een server waarop niets werkt.
test_attack_next_cannot_leave_the_site. Loop over drie next: %2F%2Fother.example%2Fwelcome, %2F%5Cother.example en https:%2F%2Fother.example%2F. Log voor elk ervan ana in met een nieuwe state en controleer met een assert dat de status 303 is en dat location precies / is. De 303 is de eerlijke helft: de login werkte.
test_attack_cover_name_cannot_leave_the_folder. Deze test brengt zijn eigen map mee. Pytest geeft elke testfunctie met een tmp_path een nieuwe tijdelijke map, als Path. Begin zo:
def test_attack_cover_name_cannot_leave_the_folder(tmp_path):
"""A cover name with an encoded ../ in it must not reach a file outside the covers folder."""
covers = tmp_path / "covers"
covers.mkdir()
(covers / "kindred.txt").write_text("a cover", encoding="utf-8")
(tmp_path / "secret.txt").write_text("not a cover", encoding="utf-8")
state = reading_server.sample_state()
state["covers_dir"] = covers
Controleer daarna 200 voor GET /covers/kindred.txt. Controleer 404 voor GET /covers/..%2fsecret.txt, en dat b"not a cover" niet in die body staat.
test_attack_title_cannot_become_markup. Maak met steps.keyed_state() en een token voor ana een boek met de titel <b>Dune</b> en controleer 201. Haal haar steps.login_cookie(state, "ana", "correct horse"), stuur GET / met die cookie in een header Cookie, en controleer 200. Decodeer de body, en controleer dat "<b>" niet in de tekst staat en "<b>Dune</b>" wel.
Alle drie slagen zodra je ze schrijft, omdat de bewakingen er al zijn. Dat bewijst weinig. Zie elke test een keer falen: zet zijn bewaking met de hand uit, voer python -m pytest -q uit, en zet hem weer aan.
Zet
return valueals eerste regel insafe_redirect_target.Zet
return pathlib.Path(folder) / nameals eerste regel insafe_cover_path.Verander in
render_pagehtml.escape(book["title"])inbook["title"].
Elke keer eindigt de run met één fout, en dat is de test voor die bewaking.
Check my work doet hetzelfde, maar grondiger. Het draait elk van je vijf tests tegen een eigen server die elke bewaking heeft, waar de test moet slagen. Daarna draait het de test tegen een server waar alleen die ene bewaking ontbreekt, waar de test moet falen.
Je test voor de coverroute slaagt. Check my work zet er een server voor in de plaats waarvan de coverroute niets controleert, en je test slaagt nog steeds. Wat zegt je dat?
Eén bewaking op alles wat vertrekt
redirect() bewaakt één header. Je server schrijft er meer, zoals etag, allow en set-cookie, en volgende maand bouwt een nieuwe branch er misschien een uit iets dat een verzoek stuurde. Elk antwoord gaat door handle(), dus één
try:
for name in response["headers"]:
values = response["headers"][name]
if isinstance(values, str):
values = [values]
for value in values:
header_value(value)
except (ValueError, TypeError):
Een headerwaarde is één tekst of een http_adapter schrijft elk item van een lijst als een eigen regel. Daarom maakt de lus van een losse tekst een lijst met één item en controleert hij elk item. De except noemt ook 0, laat header_value die opwerpen, en die fout hoort in dezelfde vaste 500 te eindigen.
Jij schrijft de branch except. Die vervangt het antwoord door de vaste 500 uit hoofdstuk 6: json_error(500, ...) met dezelfde melding, cache-control ingesteld op no-store, en een lege body als de HEAD is. Zet het hele blok na de try die route() aanroept en vóór de logregel, zodat het log de status toont die werd verstuurd.
Dit is dicht falen (fail closed), de regel uit hoofdstuk 5, toegepast op uitvoer. Een headerfout in een of andere branch wordt een fout die je in het log kunt zien, en geen antwoord met een regel die iemand anders schreef. De lus controleert alleen headers. Een body wordt gecontroleerd door de code die hem schrijft, zoals render_page doet.
Mijlpalen
De drie tests, elk een keer zien falen met zijn bewaking uitgezet. Met alle bewakingen terug eindigt
python -m pytest -qmet26 passed.De lus in
handle(). De tests eindigen nog steeds met26 passed.Het hele scenario:
python pl_sec.py injection
Lees alle vier de delen. Het resultaat zegt:
--- result ---
0 of 4 attacks worked
De bevindingen. Vul in
SECURITY.mdde rijen F10 en F11 in, in je eigen woorden. F10 is de body die de eigenaar van een boek zette, en de test ervoor istest_attack_body_cannot_set_the_owner. F11 is tekst uit een verzoek die als instructies werd gelezen in een header, een doorverwijzing, een pad en een pagina, en die noemt de andere vier tests. Zet beide opclosed in 7.8.
Wat nog open staat
Kijk nog een keer naar het verwijderformulier. POST /books/delete wordt alleen door de sessiecookie geautoriseerd, en niets in het verzoek laat zien dat het van jouw pagina kwam. Hoofdstuk 8 bouwt een klein model van een browser en gebruikt dat om dat formulier vanaf de pagina van iemand anders te versturen.
Opdracht
Schrijf de laatste drie aanvalstests van het hoofdstuk, bewaak elke header die handle() verlaat, en sluit twee bevindingen.
Schrijf in
tests/test_attacks.py, onder het commentaar voor hoofdstuk 7, drie tests. Bereik de server viareading_server.handle(...).test_attack_next_cannot_leave_the_site(): log voor elkenextin%2F%2Fother.example%2Fwelcome,%2F%5Cother.exampleenhttps:%2F%2Fother.example%2Fana in metPOST /login?next=en die, en controleer met een assert dat de status 303 is en dat response["headers"]["location"] == "/".test_attack_cover_name_cannot_leave_the_folder(tmp_path): maaktmp_path / "covers"met een bestandkindred.txt, schrijfsecret.txtnaast de map, zetstate["covers_dir"]op de map, controleer 200 voorGET /covers/kindred.txt, en controleer 404 voorGET /covers/..%2fsecret.txten dat de tekst van het geheim niet in de body staat.test_attack_title_cannot_become_markup(): maak metsteps.keyed_state()ensteps.bearer_for(state, "ana", "correct horse")een boek met de titel<b>Dune</b>en controleer 201; haalGET /op met deuit steps.login_cookie(state, "ana", "correct horse")en controleer 200; controleer dat"<b>"niet in de tekst van de pagina staat en"<b>Dune</b>"wel.
Loop in
handleinreading_server.py, na detrydieroute()aanroept en vóór de logregel, overresponse["headers"]en roepheader_value()aan op elke waarde, binnen eentry. Een waarde is één tekst of eenmet teksten: controleer elk item van een lijst. Vervang in except (ValueError, TypeError):het antwoord doorjson_error(500, ...)met de vaste melding, zet zijncache-controlopno-store, en maak zijn body leeg als deHEADis.Voer
python -m pytest -quit tot het eindigt met26 passed, en voer daarnapython pl_sec.py injectionuit.Vul in
SECURITY.mdde rijen F10 en F11 in. F10 noemttest_attack_body_cannot_set_the_owner. F11 noemt minstens een van de andere vier tests van dit hoofdstuk. Beide zeggenclosed in 7.8.
Druk daarna op Check my work. Het herhaalt de controles van les 1, 2 en 4 tot en met 7 met nieuwe waarden: bodies, headerwaarden, doorverwijzingsdoelen, een eigen map met covers, een pagina gelezen met de HTML-parser van Python, en formulieren. Het zet een reading_server.make_etag, en een functie die een header met een lijst als waarde teruggeeft in de plaats van reading_server.route: handle() moet de vaste 500 met no-store antwoorden, een 500 loggen en geen regeleinde naar buiten laten, en een gewoon verzoek moet nog steeds 200 antwoorden. Het draait je vijf tests van dit hoofdstuk, op naam gekozen, tegen servers die het in de plaats zet van reading_server.handle en van de functies waarin de bewakingen zitten: elke test moet slagen tegen een server met elke bewaking, falen tegen een server waar zijn eigen bewaking ontbreekt, en falen tegen een server die alles weigert. Geen enkele test mag een verbinding openen. Het leest de rijen F10 en F11 van SECURITY.md; hun formulering wordt niet beoordeeld. Niets uit je