Beiträge XSS Lücke in Schulportal
Post
Cancel

XSS Lücke in Schulportal

Die Schulen haben in den letzten Jahren versucht, eine digitale Infrastruktur aufzubauen. Auch um mit den Eltern sicher kommunizieren zu können. Gerade die Coronapandemie hat diesen Prozess stark beschleunigt. An der Schule meiner Tochter kommt ein Webportal zum Einsatz, in dem ich mich einloggen, Elternbriefe abrufen und nachrichten an die Lehrer*innen schreiben kann. Das funktioniert auch recht gut. Damit ich nichts verpasse, bekomme ich bei jeder neuen Nachricht eine Mail, die mich darauf hinweist.

Letzte Woche ist mir aufgefallen, dass die Benachrichtigungsmail einen Link enthält, der die Emailadresse, mit der ich mich am Portal anmelde, als Parameter enthält. Dieser Parameter wird dann in das Feld für den Nutzernamen eingefügt.

Anmeldemaske mit vorbelegtem Nutzernamen, der aus dem Queryparameter übernommen wird

Vorher hatte ich den Link nie beachtet, da ich immer im Browser meine Lesezeichen nutze. So klicke ich nicht aus Versehen auf einen Phishinglink. Doch jetzt war meine Neugierde geweckt. Zwar sind Queryparameter keine Seltenheit, doch wenn sie direkt im HTML der Seite verwendet werden, kann sie unter Umständen für eine Cross Site Scripting Attacke (XSS) anfällig sein.

Wobei handelt es sich bei XSS eigentlich?

XSS

Bei Cross Site Scripting wird von Angreifer*innen Javascript in eine ansonsten vertrauenswürdige Webseite injiziert. Dieser Code wird dann im Context der Seite ausgeführt und hat die gleichen Zugriffsrechte wie der reguläre Code der Seite.

XSS Lücken können auftreten, wenn die Seite Eingaben entgegennimmt, diese Eingaben dynamisch in der Seite einfügt und sie nicht ausreichend validiert oder encodiert. Beim Validieren prüfe ich, ob eine Eingabe überhaupt in meinem Kontext Sinn macht und gültig ist. Beim Encodieren ersetzte ich alle Zeichen, die für den Einsatzzweck gefährlich sein könnten. Im Falle von HTML sind das z. B. < > ' und ".

Es gibt auch Varianten, die statt Javascript auf das veraltete Flash oder nur auf HTML setzten.

Wir unterscheiden zwei grundlegende Angriffsvarianten.

Reflected XSS

Reflected XSS Angriffe können die Seite nicht dauerhaft ändern. Stattdessen wird ein Link oder ein Request gebaut, der den Schadcode als Parameter enthält. Öffnet jemand den Link, werden die Parameter von der angegriffen Seite übernommen und der Code wird im Browser ausgeführt. Gelangt man über normale Links auf die Seite oder gibt die Adresse der Webseite im Browser ein, ist die Seite unverändert und enthält keinen Schadcode.

Die Bezeichnung Reflected kommt daher, dass der Angriffscode quasi über Bande zurückgespielt wird. Der User oder die Userin schickt ihn an die Website und bekommt ihn wieder angezeigt.

Persisted oder Stored XSS

Persisted XSS Angriffe laufen etwas anders ab. Hier wird der Schadcode dauerhaft auf dem Server gespeichert. Dies kann z. B. über einen Foreneintrag, ein Bug-Ticket oder eine Kommentarfunktion geschehen. Sobald ein Opfer den Beitrag öffnet, wird der Schadcode aus der Datenbank geladen und in der Seite eingebettet. Von dort wird er im Browser des Opfers geladen und ausgeführt. Dies geschieht immer, wenn jemand den Beitrag oder den Kommentar öffnet. Egal über welchen Link er oder sie dorthin gelangt ist. Denn die Seite selbst lädt den Schadcode.

Mein konkreter Fall

Zurück zu meinem Verdacht. Über den Queryparameter in der URL kann ich den vorbelegten Namen beliebig ändern. Das ist aber noch nicht unbedingt gefährlich. Wichtig ist, dass die gefährlichen Zeichen abgelehnt oder encodiert werden. Wie man das sicherstellen kann, erkläre ich später noch kurz.

Jeder Browser bringt Entwicklertools mit, die sich in der Regel mit der F12 Taste öffnen lassen. Dort kann man sich den Code der Seite anschauen. Das Eingabefeld für den Nutzernamen sieht wie folgt aus. Den value kann ich ändern, indem ich den Parameter in der URL ändere. Wenn ich ?username=meinQueryParameter an die URL hänge, steht auch meinQueryParameter im HTML Code.

1
<input type="email" name="username" id="inputEmail" class="form-control" placeholder="E-Mail-Adresse" required="" value="meinQueryParameter">

Erster Versuch

Damit ich Code ausführen kann, versuche ich zunächst das Inputelement zu schließen und ein Scriptelement einzufügen. Das Ziel wäre folgender Codeausschnitt.

1
<input type="email" name="username" id="inputEmail" class="form-control" placeholder="E-Mail-Adresse" required="" value=""><script>alert(test)</script>

Alert lässt ein Popup mit der Warnmeldung erscheinen. Dies ist ein beliebter Code für XSS Tests, da es nicht übersehen werden kann.

Wenn ich "><script>alert(test)</script> als username eingebe und die Seite den Wert ohne Validierung oder Encodierung übernehmen würde, käme folgender Code dabei heraus.

1
<input type="email" name="username" id="inputEmail" class="form-control" placeholder="E-Mail-Adresse" required="" value=""><script>alert(test)</script>">

Wegen "> ist das kein valides HTML aber die meisten Browser versuchen alles so gut wie möglich zu rendern und verarbeiten es trotzdem. Wenn es stört, kann ich auch "><script>alert(test)</script><a href=" als meinQueryParameter eingeben. Dadurch würde am Ende der Zeile noch ein valides Hyperlinkelement angehängt.

1
<input type="email" name="username" id="inputEmail" class="form-control" placeholder="E-Mail-Adresse" required="" value=""><script>alert(test)</script><a href="">

Allerdings sehe ich schnell, dass die Symbole < und > nicht einfach übernommen werden, sondern in &lt und &gt codiert werden. Dies verhindert effektiv, dass ich das Inputelement schließen und ein neues Element öffnen kann. Hier haben die Entwickler*innen des Portals schon einmal aufgepasst und prinzipiell mit XSS gerechnet.

Zweiter Versuch

Als Alternative zu den HTML Elementen wähle ich nun den Umweg über Events. Diese ermöglichen es im Browser mit Javascript auf Ereignisse wie das Laden der Seite oder das Klicken in ein Feld zu reagieren.

Ich entscheide mich erst einmal für onmouseover, das den Code ausführt, sobald ich mit dem Mauszeiger über das Feld mit dem Nutzernamen komme. Der Code soll mit meinem Input so aussehen.

1
<input type="email" name="username" id="inputEmail" class="form-control" placeholder="E-Mail-Adresse" required="" value="" onmouseover='alert("This is a reflected Cross Site Scriptin Attack")'">

Um das zu schaffen, muss ich das value Feld mit einem einfachen Hochkomma schließen und noch den das Bindung auf das onmouseover Event einfügen: 'onmouseover%3D'alert("This is a reflected Cross Site Scriptin Attack"). Dadurch habe ich zwar am Ende des Inputelements überflüssige Anführungszeichen, aber den Browsern ist das egal. Das = habe ich gleich dem URL-Encoding entsprechend als %3D geschrieben.

Tatsächlich wird das Hochkomma nicht encodiert und es öffnet sich ein Popup mit der Meldung, wenn ich mit der Maus über den Nutzernamen gerate.

Anmeldemaske mit vorbelegtem Nutzernamen, der aus dem Queryparameter übernommen wird

Allerdings benötigt der Angriff gerade noch eine Nutzerinteraktion. Mein Ziel ist es, dass der Code ausgeführt wird, ohne dass die Anwenderin oder der Anwender etwas klicken oder tun muss.

Finaler Proof of Concept

Um die Nutzerinteraktion loszuwerden trickse ich mit dem Autofokusparameter und dem Event onfocus. Wenn ich den Parameter autofokus="true" bei dem Inputfeld setze, wird dieses nach dem Laden der Seite fokusiert, so wie wenn jemand in das Feld geklickt hätte. Dabei wird dann automatisch die Methode, die ich für das Event registriert habe, ausgeführt.

Außerdem möchte ich bei dem Proof of Concept gleich zeigen, dass es nicht nur ein Popup ist, sondern auch gefährlich seien kann. Daher ändere ich den Code so, dass er den Nutzernamen und das Passwort anzeigt, wenn ich im Browser die Zugangsdaten gespeichert habe.

Der Code des Inputfeldes sollte nach dem Angriff so aussehen.

1
<input type="email" name="username" id="inputEmail" class="form-control" placeholder="E-Mail-Adresse" required="" value="" autofocus="true" onfocus='alert("This is a reflected Cross Site Scripting Attack. Your stored credentials are\nusername: "+document.getElementById("inputEmail").value+"\npassword: "+document.getElementById("inputPassword").value)'>

Dafür muss ich als Queryparameter folgenden Code einfügen: 'autofocus='true' onfocus='alert("This is a reflected Cross Site Scripting Attack. Your stored credentials are\nusername: "+document.getElementById("inputEmail").value+"\npassword: "+document.getElementById("inputPassword").value).

Wegen der vielen Sonderzeichen, nutze ich gleich die URL-Encodierung. Diese ermöglicht Sonderzeichen in URLs, die dort sonst nicht erlaubt sind. Damit sieht der Parameter dann so aus: %27autofocus%3D%27true%27%20onfocus%3D%27alert%28"This%20is%20a%20reflected%20Cross%20Site%20Scripting%20Attack.%20Your%20stored%20credentials%20are%5Cnusername%3A%20"%2Bdocument.getElementById%28"inputEmail"%29.value%2B"%5Cnpassword%3A%20"%2Bdocument.getElementById%28"inputPassword"%29.value%29.

Durch das Encodieren des Parameters wird auch dessen Funktion etwas verschleiert, so dass bei einem flüchtigen Blick nicht mehr offensichtlich ist, dass Code eingeschleust wird.

Tatsächlich ist funtkioniert es wie geplant und beim Aufruf der Seite, erscheint sofort ein Popup, das mir meine Mailadresse und mein Passwort anzeigt. Das Passwort habe ich nach dem Experiment geändert.

Anmeldemaske mit vorbelegtem Nutzernamen, der aus dem Queryparameter übernommen wird

Das Auslesen der Zugangsdaten funktioniert nach dem Laden bereits deswegen, weil ich sie im Passwortmanager der Browsers gespeichert hatte. Dieser fügt sie praktischer Weise direkt in die passenden Felder ein. Das Skript kann sie dort wieder auslesen und anzeigen.

Bei einem echten Angriff würde ich die Zugangsdaten an einen meiner Server schicken. Hier geht es nur um eine Verdeutlichung des Problems.

Sobald ein XSS Angriff erfolgreich ist, kann ich die komplette Seite kontrollieren. Ich kann neue Felder einfügen, existierende Löschen, Code ausführen, HTTP-Requests an Server schicken oder im Browser der Opfer Cryptowährungen schürfen.

XSS verhindern

Um Cross Site Scripting zu verhindern, muss jede Eingabe von Nutzern oder anderen Systemen validiert werden. Nur gültige Eingaben sollten erlaubt und verarbeitet werden.

Außerdem sollten alle Daten, die verwendet werden, für den jeweiligen Zweck passen encodiert werden. Wenn ich eine Eingabe für HTML benutzen möchte, muss ich sie HTML encodieren. Wenn ich sie in Javascript nutzen möchte, muss ich das entsprechende Javascript Encoding anwenden. Und wenn ich die Eingabe für meine Datenbank brauche, muss ich auch hierfür die Daten passend encodieren.

Durch die Kombination beider Techniken erreicht man guten Schutz vor einer ganzen Reihe von Angriffen. Denn nicht nur XSS basiert auf das Einfügen von Steuerzeichen.

Generell gibt es zwei Möglichkeiten. Man kann entweder eine Liste mit erlaubten Zeichen oder Daten nutzen und alles ablehnen, was nicht darauf steht. Oder man kann eine Liste mit verbotenen Zeichen und Daten führen und alles verbieten war darauf steht. Ersteres ist dabei, wenn möglich, zu bevorzugen. Wie wir im konkreten Fall gesehen haben, können zu leicht Zeichen durchrutschen, die auch auf die Verbotsliste gehört hätten. Dazu kommen noch verschiedenste Codierungen wie URL-Encoding, so dass es unwahrscheinlich ist, wirklich alle problematischen Zeichen zu erwischen. Wenn man nur mit wenigen erlaubten Zeichen arbeitet, hat man auf jeden Fall alle gefährlichen erwischt.

Das muss man zum Glück nicht alles selbst erfinden und programmieren, sondern es gibt passende Libraries für alle Sprachen und Einsatzzwecke. Mehr Informationen findet ihr bei OWASP im Cross Site Scripting Prevention Cheat Sheet.

Ende gut, alles gut

Ich habe meinen Proof of Concept Link an den Betreiber des Schulportals geschickt und eine Erklärung des Vorgehens und Links zu Hintergründen geschickt. Tatsächlich waren sie sehr freundlich, haben den Bug innerhalb eines Tages gefixt und mich gebeten, noch einmal nachzusehen, ob er auch wirklich geschlossen ist.

Das ist wirklich eine vorbildliche Reaktion. Vielen Dank dafür.

Ärger trotz Responsible Disclosure

Leider ist ein umsichtiges Verhalten von Betreibern und Verantwortlichen nicht selbstverständlich, auch wenn man sich bemüht, mit der gefunden Lücke verantwortungsvoll umzugehen und sie schließen lassen möchte, bevor man Details dazu veröffentlicht. So hat sich die CDU gegenüber der Sicherheitsforscherin Lilith Wittman richtig schlecht verhalten. Sie hatte der CDU gemeldet, dass deren Wahlkampf-App sämtliche Daten über Wahlhelfer*innen und besuchte Wähler*innen völlig ungeschützt im Internet bereitstellt. Doch anstatt sich zu bedanken und diesen Anfängerfehler zu korrigieren, hat die CDU sie für ihr Engagement verklagt. Dabei handelt es sich bei Adressdaten in Kombination mit Informationen zu politischen Gesinnung um hochsensible Daten. Mehr zu dem Fall könnt ihr auf Netzpolitik.org lesen.

Bis bald,

seism0saurus

P.S.: Ich habe den Namen der Schule sowie Logos und Hostnamen in den Bildern entfernt. Das hat zwei Gründe. Sie sind für das Beispiel nicht von Relevanz und ich poste keine Informationen über meine Kinder im Internet. Das können sie selbst entscheiden.

Dieser Blogbeitrag wurde vom Autor unter der CC BY 4.0 lizenziert.