0%

Wer darf sich verbinden?

Woher kommt diese Anfrage?

Der Server lehnt jetzt einen Punktezähler ohne das Token vor 101 ab, und er akzeptiert einen von jeder Webseite, die fragt. Lektion 1 hat die Header aufgezählt, die ein Browser für eine Seite schreibt. Einer davon beantwortet eine Frage, die das Token nicht beantwortet: Welche Seite hat den Browser dazu gebracht, diese Verbindung zu öffnen?

Origin, und eine Seite von woanders

Jeder Handshake, den ein Browser sendet, trägt einen Origin-Header, der die Website der Seite nennt, die die Verbindung geöffnet hat, zum Beispiel Origin: https://quiz.example. Das Skript der Seite kann ihn nicht ändern; der Browser schreibt ihn.

Das ist wichtig, weil Browser auf eine bestimmte Weise behandeln. Ein Browser sendet die Cookies, die er für eine Website hat, mit jeder Anfrage an diese Website, und ein -Handshake ist eine Anfrage. Angenommen, ein Quizserver identifiziert seine Punktezähler über ein Login-Cookie statt über ein Token. Ein Punktezähler meldet sich bei https://quiz.example an und besucht dann in einem anderen Tab eine Seite auf irgendeiner anderen Website. Diese Seite führt new WebSocket("wss://quiz.example/score") aus. Der Browser schreibt den Handshake, und wenn die Website erlaubt hat, dass ihr Cookie mit Anfragen reist, die andere Websites starten, hängt der Browser es an. (Moderne Browser halten die meisten Cookies von solchen Anfragen zurück, sofern die Website nichts anderes gesagt hat; wie eine Website das sagt, liegt außerhalb dieses Kurses.) Der Quizserver sieht ein gültiges Login und öffnet eine Punktezähler-Verbindung, die jetzt die Seite der anderen Website steuert. Das nennt man Cross-Site WebSocket Hijacking.

Gewöhnliche Webanfragen haben eine Abwehr, die in den Browser eingebaut ist: Eine Seite kann eine Anfrage an eine andere Website senden, aber der Browser lässt sie die Antwort nicht lesen. WebSockets fallen nicht unter diese Regel. Sobald die Verbindung offen ist, kann die Seite, die sie geöffnet hat, jede Nachricht lesen und eigene senden.

Die Anzeigetafel ist in ihrer jetzigen Form nicht dafür anfällig, weil ihr Punktezähler-Token kein Cookie ist. Ein Token im Query- ist Teil der , die die Seite gewählt hat, also müsste eine Seite auf einer anderen Website das Token kennen, um es zu senden; der Browser fügt es nie von sich aus hinzu. Das ist ein Grund, warum dieser Kurs das Token in die Query legt und nicht in ein Cookie.

Eine Seite auf einer anderen Website öffnet aus dem Browser eines angemeldeten Benutzers ein WebSocket zu deinem Server. Welchen Nachweis fügt der Browser dafür hinzu?

Eine Allowlist

Die Abwehr besteht darin, Origin zu lesen und Seiten abzulehnen, die du nicht kennst. Die Bibliothek erledigt das für dich mit einem weiteren für serve:

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

Mit origins= wird ein Handshake, dessen Origin nicht in der steht, vor 101 abgelehnt, und ebenso ein Handshake ganz ohne Origin. Der Test ist ein Enthaltenseinstest, ungefähr dieser:

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

In einem Lauf auf einem lokalen Computer, mit http://localhost:8000 als einzigem erlaubtem , bekam ein Handshake mit Origin: http://evil.example eine 403 mit dem Body Failed to open a WebSocket connection: invalid Origin header: http://evil.example.; einer ohne Origin bekam eine 403 mit Failed to open a WebSocket connection: missing Origin header.; und das aufgeführte Origin wurde durchgelassen. Ein fehlendes Origin wird ebenfalls abgelehnt, also lehnt ein Server mit Allowlist auch Skripte ab, die kein Origin senden, wie standardmäßig den Python-Client der Bibliothek, außer None steht in der Liste.

Anzeigetafel v3 baut diese Prüfung nicht ein: Ihr Token ist kein Cookie, also kann eine fremde Seite auf nichts mitfahren, was der Browser hinzufügt. Ein Server, der Punktezähler über ein Cookie identifiziert, bräuchte sie.

Diese 403 ist die, die Lektion 2 versprochen hat. Der Server weiß genau, woher diese Anfrage kam, und sagt trotzdem nein, also verlangt er keinen Nachweis. Dieselbe Anfrage noch einmal zu senden hilft nicht.

Dein Server erlaubt nur https://quiz.example, und ein Handshake kommt mit Origin: http://evil.example an. Was antwortet die Bibliothek?

Was Origin nicht kann

Origin ist nur ehrlich, weil Browser es schreiben. Jedes Programm, das kein Browser ist, schreibt jedes Origin, das es will, oder keins. Der Python-Client der Bibliothek aus Kapitel 6 sendet von einem Laptop irgendwo Origin: https://quiz.example, wenn du connect(url, origin="https://quiz.example") übergibst. Eine Allowlist schützt also die Benutzer von Browsern vor den Seiten anderer Websites. Den Server vor Skripten schützt sie überhaupt nicht. Das Token entscheidet weiterhin, wer darf, und eine Origin-Prüfung kann nur daneben stehen, es nie ersetzen.

Warum hält eine Origin-Allowlist ein Python-Skript nicht vom Werten ab?

wss://

Die kapernde Seite oben hat wss://quiz.example/score verwendet. wss:// verhält sich zu ws:// wie https:// zu http://: derselbe Handshake und dieselben , transportiert in einer -Verbindung, der Verschlüsselung, die du in Networks with Python und beim HTTPS von with Python kennengelernt hast. TLS bringt zwei Dinge. Niemand auf dem Netzwerkpfad kann den Verkehr lesen, also ist ein Token im Query-String oder in einem Header vor ihnen verborgen. Und der Client kann prüfen, dass er mit dem Server spricht, nach dessen Namen er gefragt hat. TLS ändert nichts daran, wer sich verbinden darf: Jeder kann eine wss://-Verbindung öffnen, und der Server muss weiterhin das Token prüfen und, für Browser, das Origin.

Dieser Kurs betreibt seine Server auf mit ws:// und richtet nie TLS ein. Eine echte Anzeigetafel im Internet würde wss:// verwenden, denn ein unverschlüsselt gesendetes Token kann jeder unterwegs lesen.

Anzeigetafel v3 fehlt noch ein Stück. Ein Tippfehler eines Punktezählers schließt weiterhin seine Verbindung; das Projekt verwandelt diese Fehler in Antworten und prüft, dass das Token in keiner Antwort erscheint.