Blog ·
Ein falsches Secret klingt wie Stille
Drei Überwachungsprotokolle von Hand geschrieben statt eingebunden, und ein Fehlerbild, das sich laut Protokoll nicht von einem toten Server trennen lässt.
Von Provibr
Eine Überwachungsprüfung für ein neues Protokoll hinzuzufügen sieht nach einer Abhängigkeitsfrage aus. Es gibt eine Crate für MongoDB, eine für BSON, eine, die das Drahtprotokoll von SQL Server spricht. Drei Zeilen in ein Manifest, vierzig Zeilen Klebecode, fertig.
Wir haben alle drei Protokolle von Hand geschrieben, und die Gründe sind es wert, aufgeschrieben zu werden, weil keiner davon „nicht hier erfunden“ lautet. Zwei sind Messungen, und der dritte ist ein Versprechen, das wir Kunden über die Fähigkeiten dieses Agents bereits gegeben haben.
Was eine Abhängigkeit wirklich mitbringt
Die BSON-Crate zieht einundvierzig Crates nach sich. Das ist messbar und wurde gemessen, aber die Zahl der Crates gab nicht den Ausschlag.
Die Prüfung liest vier Felder aus einer Serverantwort. Ein generischer Dokumenttyp hält die ganze Antwort im Speicher: die Hostliste, die Tags, den Zeitpunkt des letzten Schreibvorgangs, die Clusterzeit, alles, was der Server mitschicken wollte. Dann ist „wir lesen nur vier Felder“ eine Abmachung zwischen Entwicklern statt einer Eigenschaft des Codes, und Abmachungen bricht die nächste Runde, die aus einem guten Grund ein fünftes Feld braucht.
Es gibt deshalb einen Leser, der die Elemente durchläuft, die vier angeforderten zurückgibt und den Rest überspringt, ohne ihn irgendwo abzulegen. Er kann die Antwort nicht festhalten, also ist das Versprechen eine Form statt einer Gewohnheit. Dieselbe Entscheidung wie die Positivliste beim INFO-Befehl von Redis, und dieselbe, die einen vollständigen Datenbanktreiber aus der PostgreSQL-Prüfung heraushielt: ein Treiber bringt einen Abfragebauer mit, und dann existiert in der Binärdatei, die im Netz eines Kunden läuft, Code, der eine beliebige Frage an dessen Datenbank stellen kann.
Die Crate für SQL Server ist die schärfere Fassung. Sie baut für unser Releaseziel, und genau das hatten wir als Hindernis erwartet. Was sie mitbringt, ist ein zweiter TLS-Stack: eine aus der Wartung gefallene Linie neben der aktuellen, die wir bereits ausliefern. Der prozessweite Kryptoanbieter wird für die aktuelle Linie installiert und tut für die ältere nichts, also würde TLS unter einer Prüfung einen anderen Anbieter verwenden als jede andere Verbindung dieses Agents; eine Schwachstelle in jener Linie ist eine, die wir ausliefern und nicht beheben können, ohne auf den Hersteller zu warten; und die Zertifikatsregel, die in genau einem Modul steht, damit sie nicht auseinanderlaufen kann, bekäme eine zweite Umsetzung gegen eine andere Schnittstelle.
Das Überspringen ist die gefährliche Stelle
Wer selbst einen Leser für ein Binärformat schreibt, kann einen Fehler machen, der schwerer wiegt als alle anderen, und er steckt nicht im Zerlegen. Er steckt im Überspringen.
Jeder Wert in BSON hat ein Typbyte und eine daraus folgende Länge. Ein Leser, der die Typen kennt, die ihm bisher begegnet sind, und ein unbekanntes Typbyte um null Bytes überspringt, scheitert nicht: er liest den Rest des Dokuments einfach ab der falschen Stelle weiter. Alles danach ist verschoben. Man erhält plausible Werte, die nicht die gesendeten sind.
Die Längentabelle ist deshalb vollständig, mit einer geschlossenen Verweigerung für alles, was nicht darin steht. Ein unbekanntes Typbyte ist ein Protokollfehler und niemals ein Überspringen.
Dieselbe Art Fehler sitzt eine Ebene höher, beim Lesen von Zahlen. Ein MongoDB-Server antwortet mit ok: 1 als Double und einem Fehler-code als 32-Bit-Ganzzahl, im selben Dokument. Ein Leser, der nur 32 Bit kennt, übersieht das erste stillschweigend, und dann liest sich jede fehlgeschlagene Anmeldung als Erfolg. Der Leser nimmt eine BSON-Zahl in allen drei Breiten an und gibt eine Ganzzahl zurück.
Der Anmeldeschritt, der scheitern können muss
Die Prüfung meldet sich mit SCRAM-SHA-256 an und nicht mit SCRAM-SHA-1, aus zwei Gründen, die in dieselbe Richtung zeigen.
Der ältere Mechanismus speist nicht das Passwort in SCRAM ein. Er speist einen hexadezimalen MD5 aus Benutzername, einer festen Zeichenfolge und dem Passwort ein. Ihn zu unterstützen hieße, Code zu schreiben, der einen MD5 aus Zugangsdaten bildet, für einen Mechanismus, den kein aktueller Server mehr anbieten muss.
Der leisere Grund ist schlimmer. Ein Server, der beides anbietet, landete bei einem Rückfall auf dem schwächeren, ohne dass irgendetwas rot würde, und dann steht eine grüne Messung über einer Anmeldung, die schwächer ist, als der Kunde glaubt. Bietet der Server für diesen Benutzer kein SHA-256 an, verweigert die Prüfung mit einem eigenen Code und sagt es dazu.
SCRAM von Hand zu schreiben wurde in einer früheren Runde für die PostgreSQL-Prüfung abgelehnt, und der Einwand war berechtigt: der Austausch hat eine Serversignatur, die der Client prüfen soll, und eine Umsetzung, die diesen Schritt auslässt, ist gegen einen echten Server grün und gegen einen gefälschten genauso. Hier wurde der Einwand nicht wegargumentiert, sondern beantwortet. Die Signaturprüfung ist eine eigene Funktion mit eigenem Fehlercode, damit „der Schritt fehlt“ nicht dasselbe ist wie „der Schritt gelang“, und es gibt einen Test, der eine gefälschte Signatur anbietet und verlangt, dass die Anmeldung scheitert. Dieser Test wurde rot bewiesen, indem die Prüfung entfernt wurde.
Die Crate, die die PostgreSQL-Prüfung verwendet, war hier nicht brauchbar, und das ist gemessen und nicht angenommen. Ihre SCRAM-Umsetzung schreibt die erste Clientnachricht mit leerem Benutzernamen, weil PostgreSQL den Namen im Startpaket mitschickt. MongoDB hat kein solches Paket und liest den Namen genau aus diesem Feld. Den Namen nachträglich einzusetzen würde genau die Bytes verändern, über die bereits signiert wurde, und der Server würde unseren Nachweis ablehnen. Es gibt keine Schnittstelle, ihn zu übergeben.
Ein Fehler, den das Protokoll nicht benennen lässt
Nun der Teil, den kein Stub hätte finden können.
RADIUS wird mit einer Status-Server-Anfrage geprüft, die den Server fragt, ob er lebt, und sonst nichts. Die naheliegende Alternative, eine Zugriffsanfrage mit einem Testkonto, wurde verworfen: das ist ein echter Anmeldeversuch im Netz des Kunden, mit einem echten Konto, das jemand benutzen könnte, und schreibt eine Zeile in dessen Authentifizierungsprotokoll zwischen die echten Benutzer. Bei einem Intervall von einer Minute sind das eintausendvierhundertvierzig Zeilen am Tag, die nichts über den Dienst aussagen, und in vielen Aufbauten ist ein falsches Passwort ein Grund, ein Konto zu sperren, womit die Überwachung genau den Vorfall auslöst, den sie hätte kommen sehen sollen.
Dann haben wir gegen einen echten Server gemessen, was ein falsches Shared Secret bewirkt.
Nichts. Ein Server, der den Message Authenticator einer Anfrage nicht prüfen kann, verwirft das Paket still, ohne Fehlerantwort. Das steht in der Spezifikation, und es bedeutet, dass sich das Fehlerbild eines falschen Secrets nicht von einer Firewallregel, einem falschen Port oder einer toten Maschine unterscheiden lässt.
In einem Adapter ist das nicht zu beheben, denn es ist eine Eigenschaft des Protokolls. Was man tun kann, ist aufhören, so zu tun als ob. Die Meldung „keine Antwort“ nennt ein falsches Shared Secret als mögliche Ursache, im Text, den der Kunde liest, damit ein Administrator nicht einen Nachmittag in seiner Firewall nach etwas sucht, das in einer Konfigurationsdatei steht. Und antwortet der Server doch, wird der Response Authenticator geprüft, und eine Abweichung dort wird als Authentifizierungsergebnis gemeldet statt als Protokollfehler, weil sie in diesem Aufbau tatsächlich eines ist.
| Lage | Was der Kunde sieht | Warum |
|---|---|---|
| Falsches Secret, keine Antwort | Keine Antwort, mit dem Secret als möglicher Ursache | Der Server verwarf das Paket; es kam nichts zurück, was man prüfen könnte |
| Falsches Secret, eine Antwort kommt | Ein falsches Secret, als Authentifizierungsergebnis | Der Response Authenticator stimmte nicht |
| Richtiges Secret, keine Antwort | Keine Antwort | Dieselbe Stille, und sie verdient dieselben Worte |
Die mittlere Zeile ist der einzige Fall, in dem der Unterschied sichtbar wird, und sie ist nicht der häufige.
Ein Server, der nicht messbar ist, offen gesagt
Die letzte Messung ist die, die uns am wenigsten gefiel.
SQL Server erzeugt unter Linux beim ersten Start ein Zertifikat. Dieses Zertifikat ist X.509 Version 1, eine Form ohne Erweiterungen, die seit langem nicht mehr für TLS gedacht ist, und der Zertifikatsparser darunter verweigert sie. Der verschlüsselte Modus scheitert also an einer nicht unterstützten Zertifikatsversion.
Es lohnt sich, genau zu sein, warum dieser Modus überhaupt noch etwas prüft. Die Identität wird dort nicht gewogen: der Name im Zertifikat wird nicht geprüft. Die Signatur schon, denn ohne sie bleibt kein Handshake, sondern ein Austausch von Bytes mit wem auch immer geantwortet hat. Um eine Signatur zu wiegen, muss der öffentliche Schlüssel aus dem Zertifikat gelesen werden, und genau dieser Schritt verweigert.
Das Ergebnis lautet also: ein SQL Server mit seinem selbst erzeugten Zertifikat ist mit dieser Prüfung nicht messbar. Der Kunde liest den echten Parserfehler als Detail, zitiert statt umschrieben, und die Behebung ist ein echtes Zertifikat in der Serverkonfiguration, was ein Administrator, der seinen Server überwachbar macht, ohnehin tut.
Zwei Dinge wurden bewusst nicht getan. Die Signaturprüfung wird in diesem Modus nicht ausgelassen, denn das bräche das Versprechen genau bei der Einstellung, die die meisten wählen, und machte aus einer sichtbaren Verweigerung eine unsichtbare Schwäche. Und es gibt für diesen Fall noch keinen eigenen Fehlercode, weil er sich nur am menschenlesbaren Fehlertext einer Bibliothek erkennen ließe, und das ist eine Annahme über fremden Code, die sich still verschieben kann. Das ist eine eigene Runde wert, mit einer eigenen Probe.
Es gibt auch keinen Modus ganz ohne Verschlüsselung, aus einem Grund, der diesem Protokoll eigen ist: das Passwort im Anmeldepaket wird mit einem festen Exklusiv-Oder und einem Nibble-Tausch verschleiert. Das ist eine Kodierung, keine Verschlüsselung. Wer die Bytes sieht, liest das Passwort, also schickte ein unverschlüsselter Modus das Passwort eines Kunden im Klartext über dessen Netz, unter einem Namen, der anderes nahelegt.
Was die Canary fand
Alles lief gegen echte Server statt gegen Stubs: vierzehn Prüfungen gegen MongoDB ohne und mit TLS, einen echten RADIUS-Server und einen echten SQL Server. Das nützlichste Ergebnis ist langweilig. Ein falsches Passwort ist überall ein Authentifizierungsfehler und nie ein Protokollfehler. Genau diese Unterscheidung sagt einem Kunden, ob er auf seine Zugangsdaten oder auf sein Netz schauen muss, und eine frühere Runde hatte sie in der PostgreSQL-Prüfung falsch: ein Fehler während des Anmeldeschritts wurde übersetzt in „das ist kein PostgreSQL-Server“.
Was wir aufschreiben würden
- Zählen Sie, was eine Abhängigkeit festhält, nicht nur was sie kostet. Ein generischer Dokumenttyp macht aus „wir lesen vier Felder“ eine Abmachung statt einer Eigenschaft.
- Ein Treiber bringt einen Abfragebauer mit. Lautet das Versprechen, dass keine beliebige Abfrage gesendet werden kann, sollte der Code dafür in der Binärdatei nicht existieren.
- Zwei TLS-Stacks in einer Binärdatei sind ein Regelproblem, kein Größenproblem. Der, den Sie nicht gewählt haben, ist der, den Sie nicht patchen können.
- In einem selbst geschriebenen Leser ist die Tabelle für das Überspringen der gefährliche Teil. Ein unbekannter Typ muss ein Fehler sein; null Bytes zu überspringen verschiebt alles danach.
- Nehmen Sie eine Zahl in jeder Breite an, die das Protokoll erlaubt. Ein Feld, das als Double ankommt, genügt, um jeden Fehlschlag als Erfolg zu lesen.
- Ein Rückfall auf einen schwächeren Mechanismus muss eine Verweigerung sein, kein Rückfall. Grün über einer schwächeren Anmeldung ist schlimmer als Rot.
- Macht ein Protokoll einen Fehler ununterscheidbar, sagen Sie es in der Meldung. Der Wortlaut ist die einzige Stelle, an der die Unterscheidung noch möglich ist.
- Manche Server sind nicht messbar, und das gehört in die Dokumentation statt in einen Umweg. Der Umweg wäre hier gewesen, keine Signaturen mehr zu prüfen.
Zur Klarheit: die drei Prüfungen sind gebaut, gegen echte Server getestet und stehen hinter einem Rolloutschalter. Unbewiesen bleibt, dass sie zu dritt über die Leitung zu einem echten Kundenagenten gehen, RADIUS-Accounting jenseits der Statusanfrage, und SQL Server in anderen Aufbauten als einer einzelnen Instanz auf Linux.
Aus der Plattformarbeit an Provibr. Wie derselbe Canary-Ansatz zwei Defekte in den früheren Datenbankprüfungen offenlegte, steht in ein Kanarienvogel, der gegen echte Server läuft, und wie die Zugangsdaten dieser Prüfungen zum Agenten gelangen, steht in ein Geheimnis, das einmal reist.