SSH als Ersatztechnik für `sudo`
(whynothugo.nl)- Da
sudounddoasauf setuid-Binärdateien und Privilegieneskalation angewiesen sind, handelt es sich hier um ein Experiment, bei dem ein lokalersshdan einen Unix-Domain-Socket gebunden wird und die Ausführung von Root-Befehlen übernimmt - Ziel ist, dass nur autorisierte Benutzer Root-Befehle ausführen können, ohne der gesamten Benutzersitzung dauerhaft die Fähigkeit zur Privilegieneskalation zu geben
- Die Implementierung besteht aus einer nur für Root bestimmten SSH-Schlüsseldatei, dem zugriffsbeschränkten
/run/sshd/sshd.sockund einer separatensshd-Instanz mit den OptionenAuthorizedKeysFileundPermitRootLogin=yes - Da
sshkeine Option hat, um einen bestehenden Socket direkt zu übergeben, wurden zunächstProxyCommandundsocatverwendet; später kamen ProxyUseFdpass und ein kurzes Python-Skript hinzu, um den Socket-Dateideskriptor zu übergeben - Der Ansatz funktioniert, und die Sicherheitsbehandlung stützt sich hauptsächlich auf OpenSSH; für den täglichen Einsatz ist es jedoch sinnvoller,
passfd.pyin eine kleine ausführbare Datei zu verwandeln und den gesamtenssh-Befehl in einen Wrapper zu packen
Strukturelle Einschränkungen von sudo und doas
sudounddoassind aufsetuid-Binärdateien und Privilegieneskalation angewiesen, um Befehle als Root auszuführen- Dieses Design hat einige Grenzen
- Die gesamte Benutzersitzung muss die Fähigkeit behalten, Privilegieneskalation durchzuführen
- Es funktioniert nicht, wenn die komplette Benutzersitzung in einem eingeschränkten User-Namespace läuft
setuid-Binärdateien schränken die systemweite Sicherheitskonfiguration ein
s6-sudodist eine Alternative, die Programme in einen privilegierten Server und einen unprivilegierten Client aufteilt
Ziel des Experiments
- Lokal
sshverwenden, um eine ähnliche Rolle wiesudozu übernehmen, ohne die betreffendesshd-Instanz dem Netzwerk auszusetzen - Es gibt zwei Kernbedingungen
- Nur autorisierte Benutzer dürfen Befehle als Root ausführen
- Es wird keine Privilegieneskalation verwendet
Konfiguration eines sshd für lokale Root-Logins
- Einen dedizierten SSH-Schlüssel nur für die Root-Authentifizierung erzeugen und ihn nicht in
authorized_keys, sondern in/root/.ssh/local_keysspeichern - Eine separate
sshd-Instanz an einen Unix-Domain-Socket binden und die Berechtigungen von/run/sshd/so einschränken, dass unautorisierte Benutzer nicht auf den Socket zugreifen können - Das Ausführungsbeispiel erstellt mit
s6-ipcserver/run/sshd/sshd.sockund übergibtsshddabei die folgenden OptionenAuthorizedKeysFile=/root/.ssh/local_keysPermitRootLogin=yes
/etc/ssh/sshd_configwird nicht geändert- Denn beim bestehenden, ans Netzwerk gebundenen
sshdsoll Root-Login nicht erlaubt sein - In der vorhandenen Konfiguration bleibt
PermitRootLogin nobestehen
- Denn beim bestehenden, ans Netzwerk gebundenen
Sperrung des Root-Kontos und Umgang mit Passwort-Login
- Das Root-Konto war vollständig gesperrt, sodass keine Login-Methode funktionierte; dies geschah durch ein
!vor dem Passwort-Hash sshdinterpretiert dieses Präfix!als Kontosperre und erlaubt deshalb keinen Root-Login- Der Root-Passwortwert in
/etc/passwdwird geändert, indem!durch*ersetzt wirdsshdinterpretiert*nicht als speziellen Sperrwert*passt zu keinem Passwort-Hash, daher bleibt Passwort-Login praktisch deaktiviert
- Zusätzlich ist in
sshd_configPasswordAuthentication nogesetzt; es ist sicher, passwortbasierte Authentifizierung insshdseparat abzuschalten
Verbindung zu einem Unix-Socket mit ssh
sshdhat ein-i-Flag, mit dem ein bestehender Socket übergeben werden kann, abersshbesitzt kein entsprechendes Flag- Zunächst wurde mit
ProxyCommandundsocateine Verbindung zu/run/sshd/sshd.sockhergestellt - Der Verbindungsbefehl besteht aus den folgenden Elementen
ProxyCommand='socat STDIO UNIX-CONNECT:/run/sshd/sshd.sock'- Den dedizierten Root-Schlüssel mit
-i .ssh/root-key.pubangeben - Verbindung zu
root@root - In das aktuelle Verzeichnis wechseln und dann eine Login-Shell starten
- Der verwendete SSH-Schlüssel war ein hardwaregebundener Schlüssel, daher musste zur Bestätigung der Verbindung auf ein physisches Gerät getippt werden
- Alternativ wäre auch ein
ssh-agentmöglich, der den Schlüssel nur nach expliziter Bestätigung freigibt, zum Beispielhissh-agent
socat-Overhead und ProxyUseFdpass
socatliest alle Eingaben vonsshund schreibt sie dann an den Socket, wodurch der Verbindungs-Overhead effektiv doppelt anfälltProxyUseFdpasssorgt dafür, dass ein Befehl den Socket-Dateideskriptor überstdoutansshsendet undsshdann über diesen Socket verbindet- Auf Basis eines Beispiels zur Verwendung von OpenSSH ProxyUseFdpass aus dem Jahr 2016 wurde das Python-Skript
passfd.pygeschrieben- Verbindung zum Unix-Domain-Socket
/run/sshd/sshd.sock - Übergabe des Dateideskriptors mit
sendmsgundSCM_RIGHTS
- Verbindung zum Unix-Domain-Socket
- Danach bestand der Verbindungsbefehl darin,
passfd.pyinProxyCommandanzugeben undProxyUseFdpass=yeshinzuzufügen nc -FU /run/sshd/sshd.sockscheint ebenfalls möglich, aber laut Handbuch darf-Fnicht zusammen mit-Uverwendet werden
Fazit und Form für den Praxiseinsatz
- Diese Technik funktioniert, und die sensible Sicherheitsbehandlung wird weitgehend OpenSSH überlassen
- OpenSSH hat eine gute Bilanz und unterstützt mehrere Authentifizierungsmethoden, darunter hardwarebasierte SSH-Schlüssel
- Die Einrichtung auf einem neuen Host erfordert keine komplexen Schritte, und der
ipcserver-Befehl kann über den System-Service-Manager ausgeführt werden passfd.pyist eher ein schneller Hack für das Experiment- Für den täglichen Einsatz ist es besser, eine kleine ausführbare Datei mit derselben Funktion in
/usr/local/binabzulegen und auch den gesamtenssh-Befehl in einen kleinen Wrapper zu packen
1 Kommentare
Meinungen auf Hacker News
Der wichtigste Einwand gegen diesen Ansatz ist die höhere Komplexität. Statt eines einzelnen
suid-Binaries, das eine Konfigurationsdatei liest undexec()aufruft, gibt es nun ein Binary, das als root läuft und auf einem UNIX-Socket lauscht, sowie ein weiteres Binary, das mit diesem Socket spricht; beide müssen obendrein asymmetrische Kryptografie verarbeiten.Wenn das Kernargument gegen sudo/doas lautet: „Es gibt ein für alle Nutzer zugängliches
suid-Binary, und wenn es einen Bug hat, kann es zur Rechteausweitung missbraucht werden“, dann kann man es so machen:chgrp wheel /usr/bin/sudochmod o-rwx /usr/bin/sudoWenn man sudo-Nutzer in die Gruppe
wheelaufnimmt, können nur sudo-berechtigte Nutzer die Datei-Bytes von der Platte lesen und sie auch ausführen. In Bezug auf Zugriffskontrolle ist die Sicherheit fast dieselbe wie beim sshd-Ansatz, bei dem nurwheel-Nutzer auf den UNIX-Socket zugreifen können, die Komplexität ist aber deutlich geringer.Außerdem kann der sshd-Ansatz den root-Zugriff nicht wie sudo auf bestimmte Befehle beschränken; selbst wenn es also einen Bug gäbe, der sudos Befehlsbeschränkungen umgeht, würde er nicht mehr Rechte gewähren als der sshd-Ansatz.
Wenn man befürchtet, dass der Paketmanager die Rechte von
/usr/bin/sudokaputtmacht, kann man sie regelmäßig per cron korrigieren lassen oder sudo komplett entfernen und aus den Quellen manuell an einem anderen Ort installieren. Dann muss man Wartung und Upgrades natürlich selbst übernehmen./etc. Ob Softwareinstallation/-Upgrade oder menschliche Änderung: alles wird protokolliert. Auch beim Upgrade auf ein neues Release ist es nützlich, weil man aus den lokalen Änderungen Patches erstellen, sie auf eine saubere Installation anwenden und Konflikte per 3-way Merge prüfen kann.https://etckeeper.branchable.com/
wheelaufzunehmen, sodass nur sudo-berechtigte Nutzer die Datei lesen und ausführen können, ist ziemlich vernünftig. Ich frage mich, warum das nicht überall die Standardeinstellung ist./usr/bin/sudonicht auch immutable machen? Das würde wohl helfen, zu verhindern, dass der Paketmanager daran herumfummelt.Ist das nicht genau das, was systemd run0 jetzt macht? systemd hat ein neues Tool namens
run0, aber eigentlich ist es nicht völlig neu: Ruft man das schon lange existierendesystemd-runüber einen symbolischen Link namensrun0auf, verhält es sich wie ein sudo-Ersatz.Der wesentliche Unterschied ist, dass es tatsächlich nicht
SUIDist. Es bittet den Service-Manager, einen Befehl oder eine Shell mit der UID des Zielnutzers auszuführen, weist ein neues PTY zu und leitet Daten zwischen dem ursprünglichen TTY und diesem PTY hin und her.Anders gesagt: Der Zielbefehl erbt nicht den Kontext des Clients, sondern läuft in einem isolierten Ausführungskontext, der neu von PID 1 geforkt wurde.
$TERMwird weitergereicht, aber das ist eine explizite Ausnahme – eher eine Allowlist als eine Blocklist.In vielerlei Hinsicht ähnelt
run0im Verhalten ehersshalssudo.https://mastodon.social/@pid_eins/112353324518585654
Übersehe ich etwas? Ich sehe nicht, inwiefern ein SSH-Login als root sicherer sein soll als die Nutzung von sudo. Mir wurde immer beigebracht, das nicht zu erlauben, daher weiß ich auch nicht wirklich, wie gefährlich es tatsächlich ist.
Hier scheint es Überlegungen zu geben, um Remote-Nutzer auszuschließen; um diesen Sicherheitsaspekt geht es mir also nicht.
Es könnte mit Punkt 3 der Einschränkungen von sudo zusammenhängen, aber zumindest gegenüber Punkt 1 sehe ich keinen Vorteil.
Ich verstehe, dass es ein Experiment ist, aber mir scheint das nicht weniger, sondern eher stärker verwundbar als sudo. Ein offener Socket-Proxy wirkt anfällig für Man-in-the-Middle-Angriffe.
Trotzdem habe ich ein paar Techniken gelernt, die mit alten Tools möglich sind, und es war gut, etwas Neues gezeigt zu bekommen.
sudo-Binary ist suid root und ein privilegiertes Binary, das nicht vertrauenswürdigen Nutzern direkt ausgesetzt ist. Wenn in sudo intern etwas schiefläuft, wird die gesamte Umgebung des Nutzers zur Angriffsfläche und kann ausgenutzt werden.Der SSH-Ansatz exponiert kein
suid-Binary, sondern nutzt die SSH-Netzwerkschicht. Er ist daher nicht weniger sicher als der Zugriff auf SSH über das Netzwerk, was als ziemlich sicher gilt.sudo bashzu starten.Auch dieser Daemon kann Fehlkonfigurationen oder Schwachstellen haben, und er reduziert mehrere nutzerbasierte Autorisierungsschichten auf eine einzige root-Ebene.
Trotzdem scheint er als sicherer betrachtet zu werden. Aus einer vernünftigen Sicherheitsperspektive ist er nicht sicherer, sondern einfach nur anders.
Tatsächlich ist es streng genommen sicherer, sich bei Remote-SSH direkt als root anzumelden, als sich erst als Nutzer anzumelden und dann
sudozu verwenden.Wenn
user@homeper ssh zuroot@servergeht, istroot@servernur dann kompromittiert, wennuser@homekompromittiert wurde.Wenn sich
user@homedagegen per ssh alsuser@serveranmeldet und dann per sudo zuroot@serverwird, reicht es, wenn entwederuser@homeoderuser@serverkompromittiert ist, damit root kompromittiert wird. Insbesondere aufuser@serverläuft häufig andere Software, etwa Daemons oder cron-Jobs.Jemandem, der über so einen Weg erfolgreich infiziert hat, sollte man keine kostenlose Rechteausweitung auf root geben – und auch keine laterale Bewegung, wie sie wegen Passwortwiederverwendung häufig vorkommt.
Das gilt natürlich nicht, wenn sudo ausschließlich im Modus für Allowlist-Befehle verwendet wird und keine Passwörter oder auf dem Remote-Host vollständig zugänglichen Zugangsdaten akzeptiert.
Was passiert, wenn ssh beim Booten nicht startet? In einer typischen Konfiguration hängt es, soweit ich mich erinnere, beim Start vom Netzwerk ab. Dann kann man sich auch an der Failsafe-Konsole nicht anmelden.
Ich sehe nicht, was man gegenüber sudo oder su tatsächlich gewinnt. Statt setuid-Binaries zu vermeiden, lässt man einen Netzwerkdienst mit Root-Rechten laufen, auch wenn er nur an einen Socket gebunden ist.
Was ist, wenn die SSD stirbt? Dann kann man sich ebenfalls nicht an der Failsafe-Konsole anmelden.
In 30 Jahren Linux-Nutzung sind mir Festplatten sehr viel häufiger gestorben, als dass ein sshd-Daemon nicht gestartet wäre; als Verhältnis betrachtet ist das Division durch null.
Wenn der sshd-Daemon des Betriebssystems zufällig nicht startet, würde ich das als Zeichen sehen, zu einem stabileren Betriebssystem zu wechseln.
Was man gegenüber sudo oder su gewinnt, ist, dass lokale Privilege-Escalation-Exploits deutlich schwieriger werden.
ttyS*-Geräte an seriellen Ports und IPMI SoL, verwenden weder sudo noch su.Solche Konsolen nutzen Programme wie
gettyoder einen Window Manager; das sind nicht-suid-Programme, die als root gestartet werden.Für Konsolen-Logins sollte man ein Root-Passwort gesetzt haben.
kubectl-Endpunkt verwenden kann.Wenn man sich anmelden kann, sorge ich dafür, dass man per setuid root werden kann. Bei einer k8s-Box ist das das Plattform-Infrastrukturteam, und der Zugriff auf die Services darauf läuft über den k8s-Berechtigungsanbieter.
Aus Sicht des Plattform-Infrastrukturteams liegen Metriken und Logs ohnehin bereits außerhalb der Box, und wenn man irgendeinen Job oder Workflow auslösen muss, nutzt man eine Pipeline.
Wenn sich trotzdem jemand einloggt und Root-Arbeiten erledigt, möchte ich ein Audit-Log haben.
Mir fällt keine meiner Boxen ein, bei der jemand mit Login-Rechten nicht volle Root-Rechte hätte.
Natürlich verstehe ich Fälle, in denen ein Dienst setuid nutzt; bei Diensten macht systemd setuid aber normalerweise, um Rechte abzusenken, nicht um sie zu erhöhen.
systems.unit=emergency.target,init=/bin/bash,rd.break=pre-pivotsetzen oder in eine Live-CD-Umgebung booten. Alle üblichen Notfall-Wiederherstellungsoptionen funktionieren.Für weniger gravierende Notfälle sehe ich auch keinen Grund, warum diese sshd-Instanz ans Netzwerk gebunden sein müsste.
Es ist etwas schade, dass nicht alle Nachteile dieses Ansatzes enthalten sind. sudo kann steuern, welche Gruppen welche Befehle ausführen dürfen, welche Argumente diese Befehle annehmen dürfen, ob das Erzeugen von Subshells erlaubt ist usw.
Mit diesem Ansatz verliert man viel von dieser feingranularen Kontrolle und verlässt sich auf vertrauenswürdige Schlüssel, die schwerer zu verwalten sind als das Bearbeiten der sudoers-Datei.
Wer sehen will, was sudo alles Erstaunliches kann, dem empfehle ich wirklich das Buch Sudo Mastery.
Das ist eine ähnliche Idee wie run0 von Systemd: https://news.itsfoss.com/systemd-run0/.
Eines der Probleme von ssh ist, dass Prozesserzeugung nicht Teil des Protokolls ist. Und weil es ein Remote-Protokoll ist, kann man keine lokalen Ressourcen an den Kindprozess weiterreichen.
Deshalb kann man kein nullterminiert getrenntes Argument-Array übergeben, keine zusätzlichen File Descriptors weiterreichen und keine ausführbare Datei angeben.
Stattdessen übergibt man der auf dem Server konfigurierten Shell einen einzelnen String. Dadurch braucht man Shell-Escaping und muss außerdem wissen, welche Shell auf der Serverseite läuft.
Um SSH als echten sudo-Ersatz zu nutzen, bräuchte man als Erweiterung etwas, das
posix_spawnnahekommt.https://bugzilla.mindrot.org/show_bug.cgi?id=2283
Stimme vollkommen zu. Ich mache etwas Ähnliches und habe es früher auch einmal in einem HN-Kommentar beschrieben.
Mein Ansatz ist etwas anders. Als physische SSH-Konsole nutze ich eine dedizierte Maschine, die sich in einem privaten LAN befindet, getrennt von den anderen Maschinen im Haus. Es ist kein Managed Switch, sondern ein normaler Switch, mit Ethernet-Kabeln und ohne Trunking.
Login ist nur per SSH möglich, und daran habe ich einen Yubikey gekoppelt.
Der Desktop-PC hat seine eigene Firewall und erlaubt nur SSH-Traffic von der IP-/MAC-Adresse dieser SSH-Konsole. Das gilt nur in dem privaten LAN, das beide gemeinsam nutzen; über ein anderes physisches LAN kann der Desktop aufs Internet zugreifen.
Der sshd-Daemon erlaubt nur Logins mit Public/Private Key und blockiert Passwort-Logins.
Wenn ich root brauche, boote ich die „SSH-Konsole“. Weil auf der Maschine fast nichts läuft, ist sie sehr schnell hochgefahren. Ich logge mich ein, rufe mit Pfeil nach oben wieder die Zeile
ssh root@...auf, drücke Enter und betätige den Yubikey.Diese SSH-Konsole und die Tastatur stehen auf dem Schreibtisch und sind immer in Reichweite.
Ob iptables/nftables + sshd in einem privaten LAN, das zudem physisch von einem anderen privaten LAN getrennt ist, sicherer oder unsicherer ist als das sudo-Binary oder su, kann jeder selbst beurteilen.
Wenn man nach dem „Warum“ fragt, würde ich antworten: „Weil ich es kann.“ Ich habe das vor so langer Zeit eingerichtet, dass ich mich nicht einmal mehr erinnere, wann es war. Wahrscheinlich habe ich vor etwa zwei Jahren begonnen, mit dieser Idee zu spielen, und nutze sie seitdem durchgehend. Es gab keinerlei Probleme.
Eine elegante Lösung für dieses Problem. Man muss Nutzer nicht wie Kinder behandeln, sollte aber zugleich mit vernünftigen Default-Einstellungen potenzielle Fehler vermeiden.
Wenn man root braucht, kann man sich auf der Konsole als root anmelden, daher halte ich nicht einmal
sufür nötig. Dieser Ansatz kommt dem Login als root auf einem Konsolen-tty so nahe wie möglich.Erstens müssen im Gegensatz zu sudo alle Nutzer das Root-Passwort kennen.
Zweitens gibt es keine Möglichkeit zu auditieren, wer sich tatsächlich angemeldet und was getan hat, wenn sich alle einfach als root einloggen.
Ich habe vor 10 Jahren etwas Ähnliches ausprobiert. Den UNIX-Socket-Teil gab es nicht; ich ließ einen separaten
sshdlaufen, der nur auf localhost lauschte, und musste mich auch nicht mitSCM_RIGHTSbefassen.Es gab weder besonders gute noch schlechte Ergebnisse; irgendwann verlor ich einfach das Interesse und übertrug die Einrichtung nicht auf den nächsten Rechner.