MITM bei der ersten SSH-Verbindung auf jedem VPS- oder Cloud-Anbieter verhindern
(joachimschipper.nl)- ssh-init-vm injiziert per cloud-init einen temporären privaten SSH-Hostschlüssel, um bei der ersten SSH-Verbindung zu einer neuen VM Man-in-the-Middle-Angriffe zu verhindern, und vertraut ihm nur so lange, wie langfristige Hostschlüssel erzeugt oder importiert werden
- Funktioniert auch auf VPS- oder Cloud-Umgebungen ohne dedizierte Schutzfunktion für den Verbindungsaufbau wie bei Hetzner Cloud; erforderlich ist nur das weit verbreitet unterstützte cloud-init
- Beim üblichen Trust On First Use kann ein Angreifer, wenn man bei der
ssh-Frage „The authenticity of host [...] can't be established”yeseingibt, den Traffic proxyen oder eine Maschine bereitstellen, die wie die VM des Nutzers aussieht - Das direkte Einfügen eines langfristigen privaten SSH-Hostschlüssels in cloud-init-Userdata hilft zwar bei der Authentifizierung der ersten Verbindung, kann aber dazu führen, dass sensibles Schlüsselmaterial über den Metadatenservice, SSRF, Systeme des Cloud-Anbieters oder den Admin-Workspace offengelegt wird
- ssh-init-vm legt den temporären Schlüssel in einem temporären Verzeichnis ab, trägt ihn nicht in
~/.ssh/known_hostsein, speichert die VM-Ausgabe nicht ungeprüft und verlässt sich auf die Hostschlüssel-Rotation von OpenSSH, um den langfristigen Schlüssel zu erfassen
Problem mit offengelegten cloud-init-Userdata
- Wenn man per cloud-init einen langfristigen privaten SSH-Hostschlüssel injiziert, kann man den öffentlichen Schlüssel in
~/.ssh/known_hostseintragen und damit die erste Verbindung authentifizieren, aber der private Schlüssel kann über mehrere Wege abfließen - Ein beliebiger Prozess innerhalb der VM kann die Userdata typischerweise über den Metadatenservice lesen; auf Hetzner-VMs kann der cloud-init-Inhalt unter
http://169.254.169.254/hetzner/v1/userdatasichtbar sein - Angreifer können Prozesse per SSRF dazu bringen, Metadaten preiszugeben, und solche Sperren gelten selbst in Umgebungen mit dedizierten Schutzfunktionen nicht immer durchgängig
- Auch auf anderen Systemen des Cloud-Anbieters können Userdata offengelegt werden; Hetzner weist in der API-Dokumentation zum Erstellen von Servern ausdrücklich darauf hin, „passwords or other sensitive information“ nicht dort zu speichern
- Auch der Admin-Workspace kann ein Ort sein, an dem cloud-init-Userdata verbleiben oder hindurchlaufen, daher schafft das Einfügen langfristiger privater Schlüssel ein Offenlegungsrisiko, solange der Schlüssel gültig ist
Sicherheitsanalyse und Bedrohungsmodell
- Annahme ist, dass man dem OpenSSH-Protokoll und seiner Implementierung vertraut und sich nicht darauf verlässt, dass der Administrator Angriffe erkennt
-
Schutz vor Netzwerkangreifern
- Geschützt werden die Integrität des Admin-Workspace und die VM
- Der Angreifer ist ein Man-in-the-Middle mit vollständiger Kontrolle über das Netzwerk und kann irgendwann, nachdem das Skript erfolgreich oder mit Fehler beendet wurde, Kenntnis über die cloud-init-Userdata erlangen
- Der Schutz besteht, weil der Angreifer das Schlüsselmaterial nicht zu einem Zeitpunkt kennt, an dem es noch wertvoll ist
- Das Skript speichert den temporären SSH-Hostschlüssel in einem temporären Verzeichnis, um eine versehentliche Nutzung zu vermeiden, und trägt ihn nicht in
~/.ssh/known_hostsein
-
Wenn der Admin-Workspace kompromittiert ist
- Der Schutz beschränkt sich auf die VM und ihren langfristigen privaten SSH-Hostschlüssel
- Es wird angenommen, dass der Angreifer das Netzwerk und den Admin-Workspace vollständig kontrolliert, aber nicht auf die eigentliche VM zugreift
- Der langfristige private SSH-Hostschlüssel befand sich nie auf dem Admin-Workspace, und da der Angreifer nicht auf die reale VM zugreift, erhält er auch nicht deren langfristigen Hostschlüssel
- Greift der Angreifer auf die reale VM zu, kann er die SSH-Hostschlüssel wahrscheinlich etwa mit
ssh root@<VM> cat /etc/ssh/ssh_host_*auslesen
-
Wenn die VM oder der Anbieter kompromittiert ist
- Der Schutz beschränkt sich auf die Integrität des Admin-Workspace
- Der Angreifer kontrolliert das Netzwerk vollständig und kann zusätzlich die VM oder den Anbieter vollständig kontrollieren
- Auch in diesem Fall schützt die Annahme, dass OpenSSH sicher ist, die Integrität des Admin-Workspace
- Als zusätzliche Abwehr schreibt das Skript die VM-Ausgabe nicht ungeprüft nach
~/.ssh/known_hosts, sondern verlässt sich auf die Schlüssel-Rotation von OpenSSH, um den langfristigen SSH-Hostschlüssel einzutragen - Dieser Ansatz verhindert, dass ein kompromittierter Host den
known_hosts-Parser mit bösartigen Daten füttert, und sorgt dafür, dass nur Schlüssel in~/.ssh/known_hostslanden, die die VM tatsächlich kontrolliert - Auch OpenSSH-Optionen wie
HashKnownHostsund künftige verwandte Optionen lassen sich korrekt behandeln
Bedingungen, unter denen ein echter Man-in-the-Middle-Angriff gelingt
- Ob ein Man-in-the-Middle-Angriff erfolgreich ist, hängt davon ab, ob der Nutzer tatsächlich bemerkt, dass alle Verbindungen von Anfang an zur falschen Maschine gehen, ob er die Eingabe eines Passworts verweigert und ob er den
ssh-Agent oder X11-Forwarding aktiviert - Vereinfacht nach ssh-mitm ist ein Erfolg wahrscheinlich, wenn der Angreifer den Nutzer täuschen kann, indem er Zugriff auf eine vom Angreifer kontrollierte Maschine statt auf den echten Zielhost bereitstellt
- Der Angriff gelingt auch, wenn der Angreifer den Nutzer dazu bringt, Informationen preiszugeben, mit denen er sich am echten Host anmelden kann
- Meldet sich der Nutzer per Passwort an der Maschine des Angreifers an, kann der Angreifer erfolgreich sein
- Meldet sich der Nutzer mit irgendeinem Authentifizierungsverfahren an und gibt danach an der Eingabeaufforderung ein Passwort ein, kann der Angreifer erfolgreich sein
- Meldet sich der Nutzer mit irgendeinem Authentifizierungsverfahren an und leitet die ssh-agent-Verbindung weiter, kann der Angreifer erfolgreich sein
- Liegen diese Bedingungen nicht vor, braucht der Angreifer zwar Zugriff auf den echten Host, um den Nutzer weiter täuschen zu können, kann sich aber nur anhand der Eingaben des Nutzers wahrscheinlich nicht am echten Host anmelden und scheitert daher eher
- Leitet der Nutzer eine X11-Verbindung weiter, kann der Angreifer unabhängig vom Authentifizierungsverfahren dennoch erfolgreich den Admin-Workspace angreifen
1 Kommentare
Lobste.rs-Kommentare
Schön, dass sich das automatisieren lässt. Ich habe es bisher manuell gemacht, indem ich den SSH-Fingerprint des Servers über einen separaten Kanal in der Konsole des Cloud-Anbieters geprüft habe
Ich verwalte nicht so viele Cloud-Instanzen, daher sind ein paar manuelle Schritte beim Provisioning für mich in Ordnung
Wenn man die DNS-Zone automatisiert hat, gibt es noch einen anderen Ansatz: Man erstellt ein Einmal-Token mit sehr eng begrenztem Geltungsbereich, das zum Beispiel nur das Anlegen eines einzelnen Records für
my-server-hostname.example.neterlaubtDieses Token gibt man per
cloud-initan den Server weiter, und der Server veröffentlicht damit seinen öffentlichen SSH-Schlüssel als SSHFP-Record im DNS. Danach kann der SSH-Client so eingerichtet werden, dass er SSHFP-Records automatisch verifiziert, wobei die DNS-Zone mit DNSSEC signiert sein mussDieser Ablauf erlaubt es dem Server, seinen privaten SSH-Host-Key zu behalten und trotzdem eine Key-Rotation zu vermeiden. Die meisten DNS-Anbieter unterstützen solche fein abgestuften Einmal-Zugriffstoken nicht, aber man könnte einen einfachen internen Webservice dazwischenschalten, der das Token prüft und dann den API-Aufruf mit einem dauerhaften, aber nicht eingeschränkten Token ausführt. Auf dieses dauerhafte Token hat der SSH-Server keinen Zugriff
Trotzdem würde ich wahrscheinlich eher SSH-Zertifikate ausstellen, als in eine DNSSEC-Domain zu schreiben, und ab diesem Punkt ist es eher eine Frage, welche Lösung besser zu welcher Umgebung passt
Mich würde interessieren, ob du Software oder Anbieter kennst, mit denen sich solche flexiblen Tokens erzeugen lassen, oder ob man dafür mehr oder weniger selbst etwas bauen muss
Ziemlich sauber. Allerdings scheinen im Datum des Artikels Monat und Tag vertauscht zu sein
Ich habe vor einiger Zeit einmal einen experimentellen Dienst gebaut, um dasselbe SSH-Henne-Ei-Problem zu untersuchen
Er erzeugt auf Anfrage SSHFP-DNS-Records; falls es interessant ist: https://github.com/tedb/sshfp
Schön, dass hier der unter VM-Anbietern einigermaßen einheitliche Metadaten-Service unter 169.254.169.254 behandelt wird. Das sieht man auch in den verschiedenen
cloudinit/source/DataSource*.py-Einträgen imcloud-init-QuellcodeIch selbst bin zunehmend von Design und Grenzen von
cloud-initermüdet. Mich interessiert, die Systemkonfiguration über lokale QEMU-VMs, entfernte Maschinen, Container und physische Hardware hinweg zu vereinheitlichenDas arch-boxes project zeigt, wie ein ArchLinux-
cloud-init-Image gebaut wird, und besteht aus einem sehr einfachen Bündel von Shell-Skripten. Wenn man diesen Ansatz mitguestfishoder µvm weiterführt, kann man für OCI-kompatible Images, Disk-Images für QEMU oder Cloud-Anbieter und das Provisioning neuer physischer Maschinen exakt dieselben Skripte verwendenMit einigen QEMU-Flags lässt sich derselbe Ansatz auch ohne Abhängigkeit von
cloud-initnachbilden. Soweit ich weiß, kann man mitsystemd.system-credentialskeine temporären Host-Keys übergeben; es gibt nur Credentials für~/.ssh/authorized_keyswiessh.authorized_keys.rootStattdessen kann man eine Unit-Datei erstellen, die in der initrd-Phase oder zusammen mit
systemd-firstboot.serviceausgeführt wird. Diese Unit-Datei kann man entweder vorab ins Image legen oder temporär persystemd.extra-unit.*-Credential einschleusen und dann über die Kernel-Commandline-Optionsystemd.wants=…aktivieren. In QEMU kann man die Existenz eines Metadaten-Services mit einem Eintrag wie-netdev user,id=metadata,net=169.254.0.0/16,dhcpstart=169.254.0.15,guestfwd=tcp:169.254.169.254:80-cmd:…nachbilden. Wahrscheinlich muss man dann aber die erzeugte Schnittstelle aktivieren, und auch das lässt sich vermutlich am besten über eine temporäre Unit-Datei lösenSo erhält man bei relativ geringer Komplexität erhebliche Flexibilität, wenn man eine konsistente Systemkonfiguration über verschiedene Arten von „Maschinen“ hinweg umsetzen will. Für genau diese Aufgabe scheint mir am sinnvollsten zu sein, dass das Image-Build-Tool Maschinen-Images mit fest eingebetteten Host-Keys erstellt und dann einen Custom-Host-Key-Rotationsskript als SystemD-Service installiert, das beim ersten Reboot oder beim Herunterfahren ausgeführt wird
systemd-HOOKin/etc/mkinitcpio.confaktiviert, für Arbeiten in der initrd SystemD-Unit-Dateien schreiben und sollte das im Grunde auch tunIn der Praxis ist das nur minimal umständlicher, als
{/etc,/usr/lib}/initcpio/hookszu schreibenDa sich
systemd-networkingundsystemd-resolvedin der initrd aber ziemlich leicht aktivieren lassen, kann die initrd die Verantwortung für den Systemstart übernehmen und Aufgaben einplanen, bevor auf das Root-Dateisystem umgeschaltet wirdAuf physischer Hardware wie Laptops passt das natürlich möglicherweise nicht gut, weil man für die WLAN-Verbindung etwas wie
NetworkManagerbraucht, aber für QEMU-VMs und gehostete VMs funktioniert es gut, und viele Aufgaben beim Systemstart passen natürlich in diesen BereichDas Ziel ist, nicht von
cloud-initabhängig zu sein, nicht an einen einzelnen Cloud-Anbieter gebunden zu sein, Konsistenz über physische Maschinen, Container, lokale VMs und gehostete VMs hinweg zu erreichen und die Abhängigkeiten praktisch auf SystemD zu reduzieren