Betroffen sind Mitglieder eines Replica Sets, also einer Gruppe von MongoDB-Servern, die Kopien derselben Daten vorhalten. Ein Mitglied, der Primary, ist der Hauptserver und nimmt alle neuen Schreibvorgänge an, die übrigen replizieren sie. Fällt der Primary aus, wählen die verbleibenden Mitglieder einen neuen. Auch die Shard- und Config-Server-Mitglieder von Sharded Clustern sind betroffen. Ein eigenständiger mongod, also ein einzelner Server außerhalb eines Replica Sets, ist es nicht.

Es handelt sich um einen Use-after-free-Fehler: Der Server greift auf Speicher zu, den er bereits freigegeben hat. Nach eigenen Angaben hat MongoDB die Lücke selbst entdeckt. Dem Unternehmen seien „keine Versuche bekannt, diese Schwachstelle auszunutzen“. Unter bestimmten Bedingungen könne der Fehler auch zur Ausführung beliebigen Codes im Serverprozess führen, so MongoDB.

Forscher von Datadog Security Labs haben den Absturz im Labor nachgestellt und ihre Ergebnisse gestern veröffentlicht. Bei verwundbaren Versionen lassen sich die beiden Anfragen auch mit aktivierter Authentifizierung nicht stoppen. Ein Angreifer kann nach Angaben der Forscher aber weder dafür sorgen, dass das angegriffene Mitglied in Rückstand gerät oder eine Wahl gewinnt, noch erkennen, wann das geschieht.

Für jeden betroffenen Entwicklungszweig gibt es eine korrigierte Version, die auch im CVE-Eintrag aufgeführt ist. Alle älteren Versionen eines Zweigs sind bis zu dessen erster Version betroffen. Cluster in MongoDB Atlas erhalten die korrigierten Versionen laut der Sicherheitsmitteilung des Unternehmens automatisch im Rahmen der Wartung. Kunden, die Wartungen aufschieben, sollen das Upgrade so bald wie möglich einspielen. Bei selbst betriebenen Installationen muss jedes Mitglied eines Replica Sets aktualisiert werden. Danach sollten Administratoren die laufende Version jedes Mitglieds prüfen, einschließlich Mitgliedern in Containern und solchen, die während der Wartung offline waren.

Der Zweig 8.2 taucht in der Liste der betroffenen Versionen nicht auf, seine neueste Version ist 8.2.12. Ein auf GitHub veröffentlichter Proof of Concept meldet jedoch, dass 8.2.12 den Fehler in einem Labortest mit einer nur für Tests vorgesehenen Einstellung weiterhin zeigte. Laut dem Lebenszyklusplan von MongoDB hat der Zweig 8.2 am 31. Juli sein Supportende erreicht; danach gibt es keine Korrekturen mehr, auch keine Sicherheitsupdates.

Solange ein Mitglied nicht aktualisiert werden kann, empfehlen die Forscher, den Netzwerkzugriff auf die Datenbank auf die Systeme zu beschränken, die ihn tatsächlich benötigen. Das begrenzt, wer die beiden Anfragen senden kann, behebt die Lücke aber nicht.

Technisch liegt der Fehler laut der Analyse von Datadog in einem Aufräumschritt. Ein Mitglied, das eine Wahl gewinnt, holt zunächst die verpassten Schreibvorgänge nach, bevor es neue annimmt. Der Fehler steckt in der Bereinigung, die am Ende dieser Aufholphase läuft. Die beiden Anfragen sind ping-Befehle mit einem Wert readConcern.afterOpTime. Jeder fordert den Server auf zu warten, bis die Replikation einen bestimmten Punkt erreicht hat, und der Server legt dafür eine ausstehende Benachrichtigung an, einen sogenannten Waiter. Das Platzieren dieser Waiter führt noch nicht zum Absturz. Sie laufen nicht ab und bleiben bestehen, bis das Mitglied neu startet, ein Rollback durchführt oder seine Primary-Rolle abgibt.

Zum Absturz kommt es erst, wenn das Mitglied, das die Anfragen erhalten hat, später eine Wahl gewinnt, obwohl es hinter einem anderen Mitglied zurückliegt. Beim Ende der Aufholphase läuft die Bereinigung dann doppelt, bereits freigegebener Speicher wird wiederverwendet, und der neue Primary stürzt ab, bevor er Schreibvorgänge annimmt. Sind diese Bedingungen erfüllt, hängt der Absturz laut den Forschern nicht vom Gewinn einer Race Condition ab.

Im Labor ließen die Forscher das Mitglied selbst in Rückstand geraten und erzwangen den Failover, indem sie mit Administratorrechten mongod-Prozesse anhielten und beendeten. Nur die beiden Anfragen kamen von einem Client ohne Zugangsdaten, der nicht in die Datenbank schreiben konnte. Unter Version 8.0.34 stürzte das Mitglied mit einem Segmentation Fault ab, der Laborcluster blieb ohne Primary. In anderen Installationen könnte ein neuer Primary gewählt werden und der Betrieb weiterlaufen. Die korrigierte Version 8.0.37 wies beide Anfragen sofort als nicht autorisiert ab, das Mitglied lief weiter. Ein Teil der Korrektur erlaubt readConcern.afterOpTime nur noch Systembenutzern. Der zweite Teil verhindert laut den Forschern, dass die Bereinigung doppelt läuft, selbst wenn bereits Waiter vorhanden sind; diesen Teil haben sie nicht selbst getestet. Codeausführung gelang ihnen nach eigenen Worten nicht. Ihre Ergebnisse zeigen auch nicht, wie oft der Angriff in anderen Umgebungen funktionieren würde.

Der Proof-of-Concept-Code reproduziert den Fehler laut seinem Autor in einem Labor-Replica-Set. Sein zuverlässiger Modus erfordert eine reine Testeinstellung, die ein entfernter Angreifer nicht aktivieren kann. Als Auswirkung nennt er Absturz oder Speicherbeschädigung, keine Codeausführung. Im Labortest mit deaktivierter Authentifizierung nahm die korrigierte Version 8.0.37 die platzierten Waiter zwar an, führte die Bereinigung aber nur einmal aus.

Hinweise auf platzierte Waiter oder einen Absturz finden sich in den strukturierten Serverlogs. Im verwundbaren Testlauf der Forscher traten folgende Einträge auf:

  • ein COMMAND-Eintrag „Slow query“ (ID 51803) für einen nicht authentifizierten ping mit readConcern.afterOpTime, der mit MaxTimeMSExpired endete
  • ein Mitglied, das die Primary-Aufholphase beginnt (ID 21359) und sie innerhalb eines Übergangs zweimal verlässt (ID 21363)
  • ein fatales Signal oder ein Backtrace mit CatchupState::abort_inlock() nach diesen Austritten

Eine normale Wahl oder ein einzelnes Verlassen der Aufholphase deutet nicht auf die Lücke hin. Da die Anfragen auch lange vor dem Failover eingehen können, sollten auch ältere Logs desselben Mitglieds geprüft werden. Ob die Anfragefelder protokolliert werden, hängt von den Logging-Einstellungen ab; das Feld attr.remote kann zudem statt der echten Client-IP eine Loopback-Adresse zeigen.