0%

Wie mag er verbinding maken?

Waar kwam dit verzoek vandaan?

De server weigert nu een scorebijhouder zonder het token vóór 101, en hij accepteert een van elke webpagina die erom vraagt. Les 1 somde de headers op die een browser voor een pagina schrijft. Een ervan beantwoordt een vraag die het token niet beantwoordt: welke pagina liet de browser deze verbinding openen?

Origin, en een pagina van ergens anders

Elke handshake die een browser stuurt, draagt een header Origin die de site noemt van de pagina die de verbinding opende, zoals Origin: https://quiz.example. Het script van de pagina kan hem niet veranderen; de browser schrijft hem.

Dat doet ertoe vanwege de manier waarop browsers met omgaan. Een browser stuurt de cookies die hij voor een site heeft mee met elk verzoek naar die site, en een -handshake is een verzoek. Stel dat een quizserver zijn scorebijhouders herkende aan een logincookie in plaats van een token. Een scorebijhouder logt in op https://quiz.example en bezoekt daarna, in een ander tabblad, een pagina op een andere site. Die pagina voert new WebSocket("wss://quiz.example/score") uit. De browser schrijft de handshake, en als de site haar cookie toestond mee te reizen met verzoeken die andere sites starten, voegt de browser het toe. (Moderne browsers houden de meeste cookies tegen bij zulke verzoeken, tenzij de site iets anders aangaf; hoe een site dat aangeeft, valt buiten deze cursus.) De quizserver ziet een geldige login en opent een scorebijhoudersverbinding, die nu door de pagina van de andere site wordt bestuurd. Dit heet cross-site WebSocket hijacking.

Gewone webverzoeken hebben een verdediging die in de browser is ingebouwd: een pagina kan een verzoek naar een andere site sturen, maar de browser laat haar het antwoord niet lezen. WebSockets vallen niet onder die regel. Zodra de verbinding open is, kan de pagina die haar opende elk bericht lezen en haar eigen berichten sturen.

Het scorebord is hier zoals het gebouwd is niet vatbaar voor, omdat zijn scorebijhouderstoken geen cookie is. Een token in de querystring hoort bij de die de pagina koos, dus een pagina op een andere site zou het token moeten kennen om het te sturen; de browser voegt het nooit uit zichzelf toe. Dat is een van de redenen dat deze cursus het token in de query zet en niet in een cookie.

Een pagina op een andere site opent vanuit de browser van een ingelogde gebruiker een WebSocket naar je server. Welk inloggegeven voegt de browser ervoor toe?

Een allowlist

De verdediging is Origin lezen en pagina’s weigeren die je niet kent. De bibliotheek doet het voor je met nog één voor serve:

serve(handler, "127.0.0.1", 0, origins=["https://quiz.example"])

Met origins= wordt een handshake waarvan de Origin niet in de staat geweigerd vóór 101, en ook een handshake zonder enige Origin. De test die ze uitvoert, is een lidmaatschapstest, ruwweg deze:

origin = request.headers.get("origin")
if origin is None or origin not in ["https://quiz.example"]:
    ...  # answer 403 instead of 101

In een run op een lokale computer, met http://localhost:8000 als enige toegestane , kreeg een handshake met Origin: http://evil.example 403 met de body Failed to open a WebSocket connection: invalid Origin header: http://evil.example.; een zonder Origin kreeg 403 met Failed to open a WebSocket connection: missing Origin header.; en de opgenomen origin werd doorgelaten. Een ontbrekende Origin wordt ook geweigerd, dus een server met een allowlist weigert ook scripts die geen Origin sturen, zoals standaard de Python-client van de bibliotheek, tenzij None in de lijst staat.

Scorebord v3 installeert deze controle niet: zijn token is geen cookie, dus een vreemde pagina kan niet meeliften op iets wat de browser toevoegt. Een server die scorebijhouders aan een cookie herkende, zou haar nodig hebben.

Die 403 is degene die les 2 beloofde. De server weet precies waar dit verzoek vandaan kwam, en zegt toch nee, dus hij vraagt niet om een inloggegeven. Hetzelfde verzoek opnieuw sturen helpt niet.

Je server staat alleen https://quiz.example toe, en er komt een handshake binnen met Origin: http://evil.example. Wat antwoordt de bibliotheek?

Wat Origin niet kan

Origin is alleen eerlijk omdat browsers hem schrijven. Elk programma dat geen browser is, schrijft elke Origin die het wil, of geen. De Python-client van de bibliotheek uit hoofdstuk 6 stuurt vanaf een laptop waar dan ook Origin: https://quiz.example als je connect(url, origin="https://quiz.example") meegeeft. Een allowlist beschermt dus de gebruikers van browsers tegen pagina’s van andere sites. Ze doet niets om de server tegen scripts te beschermen. Het token beslist nog steeds wie mag scoren, en een Origin-controle kan er alleen naast staan, het nooit vervangen.

Waarom houdt een Origin-allowlist een Python-script niet tegen om te scoren?

wss://

De kapende pagina hierboven gebruikte wss://quiz.example/score. wss:// staat tot ws:// zoals https:// tot http://: dezelfde handshake en dezelfde , gedragen binnen een -verbinding, de versleuteling die je tegenkwam in Networks with Python en bij de HTTPS van with Python. TLS geeft twee dingen. Niemand op het netwerkpad kan het verkeer lezen, dus een token in de querystring of in een header is voor hen verborgen. En de client kan controleren dat hij praat met de server wiens naam hij vroeg. TLS verandert niets aan wie verbinding mag maken: iedereen kan een wss://-verbinding openen, en de server moet nog steeds het token controleren en, voor browsers, de Origin.

Deze cursus draait zijn servers op met ws:// en zet nooit TLS op. Een echt scorebord op het internet zou wss:// gebruiken, omdat een token dat onversleuteld wordt verstuurd door iedereen onderweg kan worden gelezen.

Scorebord v3 heeft nog één onderdeel nodig. Een typfout van een scorebijhouder sluit nog steeds zijn verbinding; het project maakt van die fouten antwoorden en controleert dat het token in geen enkel antwoord voorkomt.