0%

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 is //other.example/welcome. Dat is één schone regel, en header_value laat hem door.

with Python, hoofdstuk 6, liet zien wat een client met een location doet. Hij lost de waarde op tegen de waar hij net om vroeg, en een waarde die met // 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.example noemt een host, zoals je net zag.

  • https://other.example/ is een hele URL, met een schema en een host.

  • /\other.example begint 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 volgt. De waarde moet veilig zijn voor het programma dat hem als volgende leest, en dat programma is een browser.

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- is "/\\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 de beslissing niet aan 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 op voor een paar waarden die met // 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:

  1. Alleen afdrukbare ASCII: value.isascii() and value.isprintable(). isprintable() is False voor een tab, een regeleinde en elk ander stuurteken. De ASCII-helft houdt ook een letter als é tegen, omdat http_adapter headers als Latin-1 schrijft en zo’n teken niet zou aankomen zoals het werd verstuurd.

  2. Nergens een backslash.

  3. Eén slash aan het begin, en geen twee.

  4. 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:

  1. 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, en value[:2] zijn de eerste twee.

  2. Stuur de waarde in login_response erdoorheen: redirect(303, safe_redirect_target(params["next"][0])). Houd de try eromheen.

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) geeft value onveranderd terug als alle vier regels gelden, en "/" voor elke andere . Hij werpt nooit een op en repareert nooit een waarde.

    1. De waarde is afdrukbare ASCII: value.isascii() and value.isprintable().

    2. Hij bevat geen backslash.

    3. Hij begint met één slash en niet met twee: value[:1] == "/" en value[:2] != "//".

    4. urlsplit(value) vindt geen schema en geen host: scheme en netloc zijn allebei "".

  • login_response(request, user) schrijft redirect(303, safe_redirect_target(params["next"][0])), nog steeds binnen zijn try.

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 starten; een schone next, een login zonder next en een verkeerd wachtwoord moeten antwoorden zoals eerst. Niets uit je terminal wordt beoordeeld.