Invoer die code wordt · oefening
Verwijs alleen door naar je eigen site
Les 4 zorgde ervoor dat next geen regel aan je antwoord kan toevoegen. Hij vroeg niet waar next naar wijst. Voer het scenario opnieuw uit:
python pl_sec.py injection
Het tweede deel gaat over die vraag:
--- a redirect: ana logs in, and next names another site ---
POST /login?next=%2F%2Fother.example%2Fwelcome -> 303
location: //other.example/welcome
a browser that follows this leaves your site for other.example
Het doel bevatte %2F%2Fother.example%2Fwelcome. parse_qs decodeerde elke %2F tot een slash, dus de //other.example/welcome. Dat is één schone regel, en header_value laat hem door.
location doet. Hij lost de waarde op tegen de // begint, vervangt de host. Alleen het schema blijft, dus de client gaat verder naar https://other.example/welcome.
Bekijk het nu vanaf de kant van de lezer. Iemand stuurt hem een link naar je login, met jouw hostnaam erin. Hij controleert de naam en typt zijn wachtwoord. De login is echt en slaagt, en de pagina die daarna komt, is van iemand anders. Die pagina kan op de jouwe lijken en nog een keer om het wachtwoord vragen. Een server die doorverwijst naar waar een verzoek ook maar zegt, is een open redirect.
Hoe een lokaal pad eruitziet
next bestaat om de lezer naar een andere pagina van de leeslijst te sturen. Zo’n waarde is een pad op deze site. Hij begint met één /, zoals /books of /books?limit=5.
Vier soorten waarden zijn niet lokaal:
//other.examplenoemt een host, zoals je net zag.https://other.example/is een hele URL, met een schema en een host./\other.examplebegint met één slash, maar een browser behandelt een backslash in een URL als een slash. Hij leest//other.example.Een waarde met een tab of een regeleinde erin, zoals
/, een tab en daarna/other.example. Een browser haalt tabs en regeleinden uit een URL voordat hij hem leest, en wat overblijft is weer//other.example.
De laatste twee doen ertoe vanwege wie de
Waarom wordt /\other.example geweigerd, terwijl hij met één slash begint?
Vraag het de parser, en vertrouw hem niet alleen
Je kent urlsplit van de router. Het resultaat heeft een scheme en een netloc, het hostdeel, en beide zijn leeg voor een gewoon pad. Vraag hem naar vier waarden. In Python-"/\\other.example" de tekst /\other.example.
from urllib.parse import urlsplit
for value in ["/books?limit=5", "//other.example/x", "https://other.example/", "/\\other.example"]:
parts = urlsplit(value)
print(value, "| scheme: [" + parts.scheme + "] host: [" + parts.netloc + "]")
Het drukt af:
/books?limit=5 | scheme: [] host: []
//other.example/x | scheme: [] host: [other.example]
https://other.example/ | scheme: [https] host: [other.example]
/\other.example | scheme: [] host: []
De eerste drie zijn wat je zou verwachten. De vierde is het probleem: Python ziet geen host in /\other.example, en een browser wel. Twee parsers lezen dezelfde tekst en zijn het oneens, en de browser is degene die erop handelt.
Daarom laat de urlsplit over. Hij controleert eerst zelf de vorm van de tekst, en vraagt urlsplit als laatste, als tweede mening. Dat laatste is om nog een reden belangrijk: urlsplit werpt // beginnen, zoals //[, en die heeft de slashregel al beantwoord.
Wat drukt dit af?
from urllib.parse import urlsplit
print("[" + urlsplit("//other.example/x").netloc + "]", "[" + urlsplit("/\\other.example").netloc + "]")
Sta één vorm toe
Les 1 somde de sleutels op die een boek mag hebben en weigerde de rest. Dit is hetzelfde idee voor vormen. Verzamel geen gevaarlijke waarden. Beschrijf de ene vorm die veilig is, in vier regels:
Alleen afdrukbare ASCII:
value.isascii() and value.isprintable().isprintable()isFalsevoor een tab, een regeleinde en elk ander stuurteken. De ASCII-helft houdt ook een letter alsétegen, omdathttp_adapterheaders als Latin-1 schrijft en zo’n teken niet zou aankomen zoals het werd verstuurd.Nergens een backslash.
Eén slash aan het begin, en geen twee.
Geen schema en geen host, volgens
urlsplit.
safe_redirect_target(value) geeft de waarde terug als alle vier gelden. Voor al het andere geeft hij "/" terug. Hij werpt nooit een exception op, en de login slaagt nog steeds: het wachtwoord was goed, dus een vreemde next is geen reden om hem te weigeren. De lezer komt op de voorpagina terecht.
Een next met een regeleinde is niet afdrukbaar, dus vanaf nu wordt ook die /, en de login antwoordt 303. De 400 uit les 4 is via de login niet meer te bereiken. Als je test uit les 4 die status controleert, faalt hij nu. Controleer wat de headers bevatten, zoals les 4 vroeg. header_value blijft in redirect(), voor elke andere aanroeper en voor de code die je volgende maand schrijft.
Een correct wachtwoord komt binnen met next=https://other.example. Wat moet de server doen?
Je taak
In reading_server.py:
Schrijf
safe_redirect_target(value)met de vier regels, in die volgorde. Test het begin met: value[:1]is het eerste teken, of""voor een lege waarde, envalue[:2]zijn de eerste twee.Stuur de waarde in
login_responseerdoorheen:redirect(303, safe_redirect_target(params["next"][0])). Houd detryeromheen.
Voer python -m pytest -q uit. Het eindigt nog steeds met 23 passed: de aanvalstest voor deze bewaking hoort bij het project in les 8. Voer daarna het scenario opnieuw uit:
--- a redirect: ana logs in, and next names another site ---
POST /login?next=%2F%2Fother.example%2Fwelcome -> 303
location: /
the redirect stays on this site
Het eerste deel laat nu ook een 303 met location: / zien, en de resultaatregel zegt 2 of 4 attacks worked.
Druk daarna op Check my work. Het derde deel van het scenario haalt een bestand op dat geen cover is. Dat is de volgende les.
Opdracht
Laat de login alleen doorverwijzen naar een pad op deze site.
In reading_server.py:
safe_redirect_target(value)geeftvalueonveranderd terug als alle vier regels gelden, en"/"voor elke andere. Hij werpt nooit een op en repareert nooit een waarde. De waarde is afdrukbare ASCII:
value.isascii() and value.isprintable().Hij bevat geen backslash.
Hij begint met één slash en niet met twee:
value[:1] == "/"envalue[:2] != "//".urlsplit(value)vindt geen schema en geen host:schemeennetloczijn allebei"".
login_response(request, user)schrijftredirect(303, safe_redirect_target(params["next"][0])), nog steeds binnen zijntry.
Voer python -m pytest -q en python pl_sec.py injection uit in de
Druk daarna op Check my work. Het roept safe_redirect_target() aan met waarden die je niet hebt gezien: paden op deze site, met een query, een fragment, een dubbele punt of een gecodeerde spatie, die onveranderd terug moeten komen, en waarden die een andere host noemen, een schema dragen, een backslash, een tab, een regeleinde of een letter buiten ASCII bevatten, of niet met precies één slash beginnen, die allemaal als "/" terug moeten komen. Het roept handle() aan met eigen gebruikers: een correcte login waarvan next een andere site noemt of een gecodeerd regeleinde bevat, moet 303 antwoorden met location: / en één next, een login zonder next en een verkeerd wachtwoord moeten antwoorden zoals eerst. Niets uit je terminal wordt beoordeeld.