Wenn der Passwortmanager Alarm schlägt
Kurz und knapp
Einem unserer Teammitglieder ist eine größere Zahl an Zugangsdaten kompromittiert worden. Wir haben aber schnell gehandelt, sämtliche Zugänge entzogen und eine ausführliche Bestandsaufnahme durchgeführt. Kein potenziell kompromittiertes Gerät ist noch in Betrieb; alle Zugänge wurden frisch eingerichtet. Aus dem Vorfall haben wir viel lernen können.
Wir konnten keine Anhaltspunkte dafür finden, die dafür sprechen, dass ein fremder Zugriff auf unsere U7-Hosts - und damit auf deine Daten - stattgefunden hat.
In diesem Blogpost erzählen wir euch die Details.
Erste Beobachtung
Mittwoch, 2. April, um die Mittagszeit. Eines unserer Teammitglieder versuchte sich am Webinterface eines unserer Proxmox-Cluster anzumelden. Dieser wird zunächst durch
einen vorgeschalteten oauth2_proxy
geschützt, verlangt dann nach Benutzername und Passwort, und dann schließlich noch nach einem zusätzlichen Faktor, was in diesem Fall
ein Passkey aus dem OS-nativen Passwortmanager war - oder besser gesagt: gewesen wäre. Denn unserem Teammitglied fiel auf, dass ihm sein Passkey nicht mehr zur Auswahl
angeboten wurde, was einigermaßen merkwürdig war.
Während er sich auf den Weg nach Hause machte, um seinen ebenfalls als zweiten Faktor hinterlegten Hardwareschlüssel zu holen, erhielt er auf seinem Handy Nachrichten, dass sein iCloud-Account wegen zu vieler fehlgeschlagener Logins temporär gesperrt wurde. Schließlich meldete sein Passwortmanager, dass eine dreistellige Zahl Accounts kompromittiert sei.
Sich mit dem “was genau” und “wie genau” zu beschäftigen war erstmal zweitrangig. Wenn wir vermuten, dass etwas passiert sein könnte, ist es unmittelbar Zeit zum Handeln.
Erstmal alles sperren
Umgehend nach Kenntnisnahme wurden Laptop und Handy des Mitarbeiters außer Betrieb genommen, sein SSH-Jumphost heruntergefahren, und wir haben begonnen, sämtliche
Accounts zu sperren. Das betraf alle betrieblichen Accounts, die wir bei Drittanbietern haben (z.B. Google, Slack, Opsgenie, GitHub, …); alle Accounts, die wir in selbst
gehosteten Diensten haben (z.B. Proxmox, Netbox, Request Tracker, GitLab, …); und natürlich: Auf allen Hosts seinen SSH-Principal zu entfernen und zusätzlich seinen
Public Key auf die Liste von revoked keys zu setzen (wobei uns ein unschöner Fehler unterlaufen ist, der zeitweise dazu geführt hat, dass sich weder User noch wir selbst
per SSH einloggen konnten… mit etwas Galgenhumor ließe sich sagen: kurzzeitig ein sehr sicherer
Zustand..!!). Das hat leider sehr viel Arbeit gemacht, denn auch wenn ein Rollout auf ~250 U7-Hosts auf einen Schlag erfolgen kann, so gibt es noch viele viele
Kleinst-VMs für interne Dienste (wie Netbox oder Request Tracker) oder für User (wie Webmail oder MySQL-Webadmin), die alle in eigenen Repos verwaltet werden. Hier
erweist sich als ein wenig schmerzhaft, dass jedes dieser Repos seine eigene authorized_principals
oder authorized_keys
verwaltet - aber dazu später mehr.
Keine falsche Scheu: Meldung machen
Wir haben außerdem umgehend unseren Datenschutzbeauftragten hinzugezogen und gemeinsam eine Meldung an den für uns zuständigen Landesbeauftragten für den Datenschutz und die Informationsfreiheit Rheinland-Pfalz gemacht. Interessant zu wissen: Man muss zunächst eine Einschätzung treffen, welches Risiko für die Rechte und Freiheiten der betroffenen Personen vorliegt, und wenn man jenes Risiko für “gering” hält, sind die Meldevoraussetzungen des Art. 33 DSGVO nicht gegeben. Wir haben in Absprache mit unserem Datenschutzbeauftragten daher zunächst das Risiko als “normal” angegeben, um überhaupt eine Meldung machen zu können. Da wir von Anfang an vorhatten, in jedem Fall diesen Blogpost hier zu veröffentlichen, wollten wir in jedem Fall vermeiden, dass uns ein Ausbleiben einer Meldung - weil wir das Risiko direkt als nur “gering” eingeschätzt hätten und somit keine Meldung hätten machen können - später auf die Füße fällt. Nachmelden kann man schließlich immer - egal ob sich das Risiko später als höher oder niedriger herausstellt.
Bestandsaufnahme im Detail
Nachdem die ersten Schritte erledigt waren, konnten wir uns an eine detailliertere Bestandsaufnahme machen, um besser einschätzen zu können, welchen Risiken wir - und damit auch ihr bzw. eure Daten - tatsächlich ausgesetzt sind.
Was webbasierte Dienste angeht, so betreiben wir viele interne Dienste hinter einem oauth2_proxy
oder binden OAuth2 nativ ein. In den wenigen Ausnahmen, wo ein rein
passwortbasierter Zugang ausreicht, sind dahinterliegende sensible Daten anderweitig geschützt, z.B. durch den Netbox-Secrets-Mechanismus. OAuth2 verlangt zunächst nach
einem Google-Login in unserem Firmenaccount (der wiederum auch einen zweiten Faktor verlangt). Das Passwort für den Google-Login war im Passwortmanager gespeichert.
Logins von neuen Geräten werden seitens Google protokolliert, sodass sich hier leicht sicherstellen ließ, dass hier kein neuer Login erfolgt ist.
Zentraler Punkt war das Thema SSH. Der Mitarbeiter verwendete hier ein Setup mit einem SSH-Key auf seinem Laptop, der nur für den Zugriff auf einen Jumphost verwendet wurde. Auf dem Jumphost wiederum lag ein SSH-Key, der für den Zugriff auf unsere Hosts, also insbesondere auch die U7-Hosts verwendet wurde. Beide Keys waren mit einer Passphrase geschützt. Auf dem Jumphost lief ein SSH-Agent, der den entsperrten Key zum Zeitpunkt des Vorfalls im RAM hielt. Auf dem Laptop lief kein SSH-Agent. Die SSH-Passphrase war nicht im Passwortmanager gespeichert.
Vom heruntergefahrenen Jumphost haben wir ein Image gezogen und dieses Image dann readonly auf einem anderen System gemountet und untersucht. Dabei konnten wir feststellen, dass sich alle SSH-Logins auf dem Jumphost sowohl von der Quell-IP als auch von den Uhrzeiten her mit dem Tagesablauf des Mitarbeiters decken. Sie bieten also keinen Anlass, davon auszugehen, dass ein fremder Login auf dem Jumphost stattgefunden hat. Wir haben das Image auch einem vollständigen Scan mit ClamAV unterzogen und keine Auffälligkeiten gefunden.
Der Mitarbeiter besaß auch einen GnuPG-Key, der Zugriff auf einen gopass-Passwortspeicher ermöglicht. Auch dieser GnuPG-Key ist mit einer Passphrase geschützt. Es lief kein gpg-agent, der den Key zum Zeitpunkt des Vorfalls entschlüsselt im RAM gehalten hätte, und der Mitarbeiter hat ihn auch seit Wochen nicht mehr verwendet. Die GnuPG-Passphrase war nicht im Passwortmanager gespeichert.
Unsere Einschätzung
Vorausgeschickt: Eine Einschätzung kann immer nur eine möglichst gut begründete Vermutung auf Basis dessen sein, was wir sehen und geprüft haben. Wir sind hier bemüht, so gründlich wie möglich vorzugehen - aber das hat auch Grenzen. Wenn wir als Hypothese davon ausgingen, dass ein Laptop mit einer Hardware-Backdoor ausgeliefert würde und mit dieser potenziell schon seit Jahren alles mitgeschnitten würde, was jemand tut, dann… könnten wir und eigentlich auch alle anderen Firmen strenggenommen direkt dicht machen. An irgendeinem Punkt muss man schlicht und einfach “wird schon so passen” sagen und ihn als “für die in Frage kommenden Zwecke ausreichend sicher” annehmen.
Einen Zugriff auf den Google-Account des Mitarbeiters können wir aufgrund der Google-Login-Protokolle ausschließen. Das ist schon mal eine große Erleichterung, weil das
ausschließt, dass ein Zugriff auf unsere webbasierten Dienste stattgefunden hat, die wir hinter einen oauth2_proxy
gesetzt haben oder die nativ OAuth2 als
Authentifizierungsmechanismus nutzen.
Der Hardwareschlüssel des Mitarbeiters, der überall, wo es möglich ist, als zweiter Faktor fungiert, befindet sich nach wie vor in seinem Besitz.
Das Verschwinden des Passkeys im Passwortmanager stellt zugegebenermaßen noch ein Rätsel dar. Eine Extraktion des Passkeys aus dem Passwortmanager ist unserer Kenntnis nach aber nahezu ausgeschlossen; er kann unserer Kenntnis nach auch nur zusammen mit Biometrie oder dem Zugangscode des Geräts (unter iOS der Bildschirm-Entsperrcode, unter macOS das User-Passwort) verwendet werden. Aber so oder so: Der Passkey ist nun auch nirgendwo mehr als zweiter Faktor hinterlegt, ausschließlich der Hardwareschlüssel.
Einen Zugriff auf den SSH-Key des Laptops halten wir für unwahrscheinlich. Er ist mit einer Passphrase geschützt, die nicht in einem ssh-agent vorgehalten wurde; die Passphrase wurde ausschließlich aus dem Kopf eingegeben. Dieser SSH-Key hatte auch keinen Zugriff auf unsere Systeme - nur auf den Jumphost des Mitarbeiters. Selbst wenn der SSH-Key entwendet worden wäre, wäre er nun nutzlos, da er auf dem neu aufgesetzten Jumphost nicht mehr autorisiert ist.
Einen Zugriff auf den SSH-Key des Jumphosts halten wir für unwahrscheinlich. Zwar war die Passphrase in einem ssh-agent vorgehalten, allerdings konnten wir bei einer Offline-Analyse des Jumphost-Images keine Fremd-Logins nachvollziehen. Ehrlicherweise besteht bei einem Login mit root-Rechten die Möglichkeit, die entsprechenden Login-Logs zu kompromittieren. Uns sind aber zumindest keine Anzeichen dafür aufgefallen. Sollte der SSH-Key entwendet worden sein, so ist er nun nutzlos, da der darin enthaltene SSH-Principal auf allen unseren Hosts entfernt wurde und der Key in die Liste der revoked keys eingetragen worden ist.
Einen Zugriff auf den GnuPG-Key des Mitarbeiters halten wir für unwahrscheinlich. Weder war die Passphrase in einem Passwortmanager gespeichert, noch wurde sie in einem gpg-agent vorgehalten; sie wurde auch seit Wochen nicht mehr eingegeben. Wenn der GnuPG-Key kompromittiert wäre, so würde dies unseren historischen Passwortstore betreffen. Hier muss ehrlicherweise gesagt sein, dass wenn da ein Kind in den Brunnen gefallen ist, dann ist das nicht mehr reversibel. Da wir aber schon seit geraumer Zeit Passwörter zunehmend an passendere Stellen verschieben (z.B. verschlüsselte Secrets für Ops-Systeme in unserer selbst gehosteten Netbox, verschlüsselte Ansible Vaults für Deployments in unserem selbst gehosteten GitLab, verschlüsselte sonstige Passwörter in unserem selbst gehosteten Vaultwarden) finden sich in diesem Passwortspeicher auch nur noch wenige Passwörter, die überhaupt noch Relevanz haben. Wir nehmen den Vorfall aber zum Anlass, den Passwortstore nun noch dringlicher loszuwerden, ihn zunächst unter der Perspektive “mit welchen Passwörtern könnte man gewichtige Dinge tun” durchzusehen, alle diese Passwörter zu ändern und dabei in unser Vaultwarden zu überführen, das im Gegensatz zum alten Passwortstore auch team-bezogene Tresore verwendet, damit Zugriffsrechte minimiert werden.
Zurück an die Arbeit
Laptop, Handy und Jumphost des Mitarbeiters wurden komplett neu aufgesetzt, neue SSH-Keys erstellt und von unserer SSH-CA signiert, seine Passwörter geändert. Viele Zugänge sind noch gar nicht wieder neu vergeben, weil wir eine Balance suchen zwischen “der Mitarbeiter muss grundlegend wieder arbeiten können” und “er soll aber noch keine Zugriffe erhalten, die er nicht jetzt sofort zwingend braucht”. Bei der Priorisierung stand insofern die Wiederbeschaffung der Arbeitsfähigkeit erst unten auf der Liste.
Unsere Learnings
Eine (bessere) Security Policy muss her
Es lässt sich nicht beschönigen: Eine niedergeschriebene Security Policy haben wir bisher nicht - in erster Linie dem Umstand geschuldet, dass alle bei uns mehr oder minder “vom Fach” sind und wir uns nicht mit Policies á la “Verwende bessere Passwörter als admin123”, “Speichere Passwörter nicht in einer unverschlüsselten Textdatei” und auch “Gib deinem SSH-Key eine Passphrase” aufhalten müssen, weil das für alle absolute No-Brainer sind. Und Dinge, die ohnehin organisationsweit durch Software erzwungen werden, wie z.B. verpflichtende zweite Faktoren, brauchen auch nicht mehr in einer Policy niedergeschrieben zu werden.
Spannend wird es dann aber, wenn es an Details geht, die vielleicht für die einen “na ganz klar auf überhaupt gar keinen Fall” bedeuten, für andere aber “ich seh das Risiko nicht”, oder um Maßnahmen, von denen absehbar ist, dass sie in der Praxis nicht eingehalten werden (wie z.B. ein hypothetisches “vergib eine Passphrase von mindestens 50 Zeichen, aber du musst sie immer per Hand eingeben - zwanzig Mal am Tag” - da liegt auf der Hand, dass die persönliche Praxis schnell irgendwie anders aussehen wird). Es gibt Designentscheidungen, die man so oder so treffen kann: Wollen wir alle dazu verpflichten, ihre betrieblichen Passwörter ausschließlich in unserem selbst gehosteten Vaultwarden zu speichern? Oder erlauben wir auch die Verwendung lokaler Passwortmanager - an die wir zwar Anforderungen stellen können (muss verschlüsselt sein, muss einen zweiten Faktor verlangen, darf nicht in die Cloud gesynct werden, …) die wir aber nicht forcieren können? Was ist mit der Passphrase, die man zum Login in Vaultwarden braucht - darf man die speichern? In Vaultwarden ist ja Quatsch - aber wo dann?
Andere Vorgehensweisen erscheinen erstmal unkritisch oder sogar sinnvoll, bis man sie zu Ende durchdacht hat. Wenn ich zum Beispiel ein Passwort für einen Dienst in einem Passwortmanager speichere und bei jenem Dienst meinen Hardwareschlüssel als zweiten Faktor hinterlege, dann werde ich in der Regel dazu aufgefordert, Recovery Codes zu generieren. Und wo packe ich die dann hin? Na klar, in den Passwortman..waaaait. Dann wären Passwort und Recovery Codes an einem Ort und damit gäbe es de facto überhaupt keinen zweiten Faktor mehr.
Machen wir uns nichts vor: Wenn man ganz hart nachdenkt und sehr in sich geht, dann findet vermutlich jeder und jede die eine oder andere Stelle, wo man… nicht ganz optimal handelt. Uns ist wichtig, hier dann nicht mit “Wie kannst du nur..!!”-Schelte zu kommen, denn am Ende ist es ja in unserem Interesse, wenn Teammitglieder offenherzig zugeben können, wo sie suboptimal gehandelt haben. Immerhin ist dann das daraus resultierende Risiko ja bereits vorhanden, egal wie man es dreht und wendet. Der Fokus muss also sein, das Risiko nun wieder abzuwenden und aus dem bisherigen Vorgehen Learnings zu ziehen, die für alle relevant sind.
Der aktuelle Vorfall hat uns gewissermaßen in dieser Hinsicht aufgeweckt, uns die Mühe zu machen, einige dieser Details dann doch mal konzentriert zu durchdenken, abzustimmen, zu entscheiden und dann auch zu verschriftlichen. Beispielsweise, dass wir als zweite Faktoren wirklich nur Hardwareschlüssel zulassen wollen, aber keine Passkeys (Software). Oder auch, dass OTP-Generatoren nur dann verwendet werden dürfen, wenn sie auf einem System laufen, das den Basisschlüssel nicht anderswohin synchronisiert und das nicht gleichzeitig auch Zugriff auf den Passwortmanager hat. Ein ganz wichtiger Faktor dabei ist - und da liefert der aktuelle Vorfall auch das passende Mindset - dass allen verständlich ist, warum Dinge in einer Policy niedergeschrieben sind. Eine Policy soll konkreten Bedrohungsszenarien vorbeugen, an die bisher vielleicht nur das halbe Team gedacht hat, und bei dem wir ergo vermeiden wollen, dass das andere halbe Team in eine “Oops, daran habe ich nicht gedacht”-Falle tappt.
Unsere künftige Security Policy ist jetzt gerade noch in Arbeit, wenngleich schon recht weit fortgeschritten. Wenn wir so weit sind, werden wir sie gerne auch mit euch teilen. Denn wenn wir eins bei der Recherche gemerkt haben, dann dies: “Kleine” Policies, die man so exemplarisch im Internet findet, sind oftmals grotesk minimal (auf dem Level von “Ändere alle Passwörter monatlich”, aber kein Wort z.B. über zweite Faktoren), während “große” Policies oft unfassbar komplexe Standard-Frameworks sind, von denen weite Teile kaum auf uns anwendbar sind und die derart abstrakt daherkommen, dass die Beschäftigung damit einem starken Schlafmittel vorzuziehen ist - nicht gerade eine gute Ausgangslage, wenn man möchte, dass Teammitglieder auch bewusst und mit gutem technischem Verständnis die Maßnahmen jener Policy umsetzen.
Ein (besseres) Vorgehen zum Rechteentzug muss her
Nicht falsch verstehen: Wir sind überzeugt davon, den Rechteentzug des betreffenden Mitarbeiters vollständig durchgezogen zu haben. Sie von den U7-Hosts runterzukratzen war dabei der einfachste Part - andere Teile hatten gewisse Bastelqualitäten. Zum Beispiel war auch ein wenig Scripting nötig, um alle unsere git-Repos auszuchecken und sämtlichst darauf zu untersuchen, ob der betreffende SSH-Principal bzw. SSH-Key irgendwo hinterlegt war. Dort musste er dann überall manuell entfernt werden und die Änderung ausgerollt werden. Das war alles machbar - aber noch nicht so state of the art, wie wir es gerne hätten.
Hier etwas Besseres hinzukriegen, ist allerdings nicht von jetzt auf gleich machbar, zumal wir hier gerne auf ein Vorgehen kommen möchten, das möglichst transparent ist, wenig zusätzliche Komplexität mit sich bringt und auf dem aufsetzt, was wir bereits einsetzen. Wir denken da beispielsweise an ein zentrales git-Repo, das dann auch für Nachvollziehbarkeit sorgt und das als Single Source of Truth fungieren kann. Es bleibt aber noch die Schwierigkeit, dass wir unsere Projekte in vielen separaten Repos separater Teams (Ops, Dev, Support) haben. Diese können ein solches SSH-Key-Repo zwar einbinden (z.B. via git submodule, oder auch via Ansible Galaxy, die Technologie ist ja erstmal egal), aber wenn das Deployment der darin enthaltenen Keys immer noch an Dutzenden verschiedenen Stellen passieren muss, ist das erstmal nur die halbe Miete. Große Lösungen wie z.B. The Bastion von OVH sind hier sicherlich auch interessante Ansätze, bringen aber eben auch Komplexität mit sich, die einer Teamgröße wie unserer vielleicht auch wieder nicht angemessen ist. Wir bleiben dran!
Fazit
Alle alten und potenziell kompromittierten Zugänge sind weg.
Alle alten und potenziell kompromittierten Geräte sind vollständig neu aufgesetzt.
Es ist zum jetzigen Zeitpunkt nicht eindeutig zu klären, ob ein Gerät des Mitarbeiters kompromittiert wurde, oder aber sein Passwortmanager, oder aber sein iCloud-Account, oder aber sein früherer Passwortmanager. Da all jene Szenarien aber zu einem sehr ähnlichen, wenn nicht identischen Vorgehen geführt hätten, ist es für uns akzeptabel, es nicht final klären zu können, da wir daraus Konsequenzen gezogen haben, die jedem dieser Szenarien Rechnung tragen können.
Wir sind hinreichend sicher, dass nichts passiert ist. Die Gründe dafür, dass wir da hinreichend sicher sind, haben wir oben - hoffentlich plausibel nachvollziehbar - dargelegt.
Wir haben im Prozess einige Verhaltensweisen identifiziert, die aufgezeigt haben, dass wir unerwartet teilweise unterschiedliche Betrachtungsweisen von Risiken und in der Folge unterschiedliche Verhaltensweisen hatten. Das ziehen wir in einer Policy gerade.
Foto von Jarrod Erbe auf Unsplash