Mit SSH Löcher in Firewalls stanzen
vom
SSH ist ja so etwas wie eine unerschöpfliche Quelle interessanter Erfahrungen. Ständig entdeckt man neue Features. Heute: Wie man per SSH rückwärts durch eine Firewall bricht.
Ausgangssituation
Ein Rechner steht hinter einer Firewall, und auf diesem laufen einige Dienste, die man gern von außerhalb der Firewall erreichen würde. Grundsätzlich besitzt man SSH-Zugriff auf den Rechner, allerdings wird von der Firewall jeglicher eingehender Verkehr auf den Rechner geblockt, sodass von außerhalb der Firewall keine Verbindungsaufnahme möglich ist. Umgekehrt kann man jedoch von hinter der Firewall Verbindungen aufbauen.
Lösungsweg
Da es nicht möglich ist, von außen durch die Firewall zu brechen, man selber aber auch nicht direkt hinter ihr sitzt, ist die Idee grundsätzlich, den Zielrechner, bevor er hinter die Firewall gestellt wird, so zu präparieren, dass er selbst ene Verbindung zu einem Rechner „auf der anderen Seite“ öffnet und anschließend über diese Verbindung Befehle entgegennimmt. Bei etwaigen Verbindungsabbrüchen soll diese Verbindung so schnell wie möglich wieder aufgebaut werden.
Befolgen des Lösungswegs
Erst dachte ich, ich müsste mühsam einen eigenen Dienst schreiben, der
diese Aufgabe erfüllt und Befehle von mir entegegennimmt, aber
erfreulicherweise verfügt SSH über ein Feature
namens „Remote Port Forwarding“, mit dem man einen beliebigen Port am
lokalen Rechner auf einen anderen (fast) beliebigen Port an einem
entfernten Rechner (im Folgenden beispielhaft remote.example.com
)
weiterleiten kann1. Ich
stelle mir das gelegentlich als „Reverse-Forwarding“ bzw. „Backwarding“
vor, denn effektiv ermögliche ich einem entfernten Rechner rückwärts
über die SSH-Verbindung auf meinen Rechner
zuzugreifen.
Obwohl es natürlich möglich und aus Sicherheitsgründen empfehlenswert
ist, über diese Technik nur den/die Dienst/e zu tunneln, auf die man
zugreifen möchte, ist es natürlich genauso möglich, den lokalen SSH-Dienst auf einen entfernten Rechner zu tunneln,
denn aus Sicht von SSH ist SSH auch nur ein lokaler Dienst von vielen.
Grundsätzlich funktioniert das ganze mithilfe der -R
-Option:
$ ssh user@remote.example.com -R remoteport:host:localport
Dabei sind die Werte für user
und remote.example.com
natürlich
entsprechend zu ersetzen. Hinter dem -R
folgt die Spezifikation,
welcher lokale Port (localport
) an welchen entfernten Port
(remoteport
) weitergeleitet werden soll. host
gibt an, an welchen
Rechner man den Port binden möchte (aus Sicht von
remote.example.com
!), was man in 98% der Fälle auf localhost
setzen
möchte.
Konkret kann man den SSH-Dienst des lokalen Rechners, sofern er auf dem Standardport 22 läuft, wie folgt weiterleiten:
$ ssh user@remote.example.com -R 1234:localhost:22
Danach könnt ihr euch von einem beliebigen Rechner auf
remote.example.com
anmelden und danach dort mithilfe von
$ ssh otheruser@localhost -p 1234
auf den weiterleitenden Port 1234 zugreifen, auf dem der SSH-Dienst des ursprünglichen Rechners läuft. Obwohl
ich in der SSH-Manpage nichts entsprechendes
finden konnte, hat sich die -R
-Option auch als recht robust gegenüber
Verbindungsabbrüchen bewiesen (getestet durch einfaches Ziehen des LAN-Kabels am weiterleitenden Rechner und späterem
Wiedereinstecken). Während die Verbindung unterbrochen ist, sind
selbstverständlich keine Anmeldungen von remote.example.com
möglich,
aber SSH stellt die Weiterleitung recht
schnell wieder her, nachdem die Verbindung wieder aufgebaut werden kann.
Das SSH-Kommando beendet sich nicht einmal,
sondern führt den Reconnect vollkommen transparent durch.
Soweit das grundsätzliche Vorgehen. Das ganze lässt sich noch etwas
weiter ausschmücken und automatisieren, um den Prozess zu vereinfachen.
Zunächst ist es empfehlenswert, auf dem Rechner hinter der Firewall ein
SSH-Schlüsselpaar ohne Passwort zu erstellen
und den öffentlichen Schlüssel auf remote.example.com
zu hinterlegen,
sodass der Verbindungsaufbau ohne Passwortabfrage geschehen kann
(Sicherheitshinweis: Jeder, der Zugriff auf diesen Rechner hat, hat
damit auch automatisch Zugriff auf remote.example.com
!2). Danach kann man den
eigentlichen SSH-Befehl noch etwas verfeinern:
ssh user@remote.example.com -R 1234:localhost:22 -N -o ExitOnForwardFailure=yes -f
-N
weist SSH an, kein Kommando auf dem
entfernten Rechner auszuführen, was praktisch ist, weil wir ohnehin nur
Ports weiterleiten wollen. -o ExitOnForwardFailure=yes
aktiviert die
ExitOnForwardFailure
-Option, die SSH
anweist, mit einem Errorstatus zu beenden, wenn die Portweiterleitung
nicht aufgebaut werden kann (standardmäßig wird nur eine Warnung auf der
Standardfehlerausgabe ausgegeben und der Befehl läuft weiter, was wegen
-N
ziemlich sinnlos ist). -f
schließlich instruiert SSH zu *f*orken, sprich, sich in den Hintergrund
zurückzuziehen, wenn der Verbindungsaufbau erfolgreich war (anderenfalls
endet der Befehl sofort mit einem Errorstatus). Somit ist es nicht
erforderlich, eine ständige Verbindung zu dem weiterleitenden Rechner
aufrecht zu erhalten. Alternativ kann man ExitOnForwardFailure
auch
über die Konfigurationsdatei ~/.ssh/config
festlegen.
Ziel erreicht
Das ganze könnte man nun in einen Systemd-Service (oder etwas Ähnliches
für Upstart, SysV-Init, etc.) wrappen und automatisch beim Booten
starten lassen, nachdem das Netzwerk hochgefahren und verbunden ist. Ist
dies getan, so kann man den Rechner getrost hinter die Firewall stellen
und hat trotzdem noch Zugriff auf den Rechner, indem man sich einfach
auf remote.example.com
verbindet und von dort über Port 1234 weiter
auf den Zielrechner.
Valete.
1 Es gelten die üblichen Verdächtigen: Für Ports
< 1024 muss man sich auf remote.example.com
als root anmelden, was
man nach Möglichkeit vermeiden sollte; außerdem darf der gewünschte
Port, in diesem Posting beispielhaft 1234, natürlich nicht schon von
einem Dienst belegt sein.
2 Obwohl ich es nicht getestet habe, sollte es
möglich sein, den Nutzer des weiterleitenden Rechners bei der SSH-Anmeldung auf remote.example.com
in ein Chroot
zu stecken, um die Auswirkungen eines Fremdzugriffs auf den
weiterleitenden Rechner zu minimieren. Schließlich möchte man nicht
gleich einen Server, den man für diesen Anwendungsfall
höchstwahrscheinlich benutzen wird, schutzlos dem Shellzugriff
unbekannter Dritter überlassen, selbst wenn es nicht Root-Zugriff ist…