Teil 1: Implementierung in der Linux-Kernelfirewall
Teil 2: Integration in SSH-Clients
Teil 3: Noch nicht veröffentlicht.
Zum Ende des ersten Teils des Tutorials waren wir in der Lage, für den Verbindungsaufbau zum Server dessen SSH-Dienst durch Anklopfen manuell in der Firewall freizugeben. Diesen zusätzlichen Arbeitsschritt gilt es nun einzusparen, indem das Anklopfen in den SSH-Client integriert wird.
Dabei betrachten wir Beispiele für verschiedene Integrationsmethoden für die beiden beliebtesten SSH-Clients unter Linux (und anderen Unix- und Unix-artigen OS) sowie Windows; OpenSSH und PuTTY.
Wie auch im vorhergehenden Teil senden wir das Credential lduiSyvSwYZzwp9OfPiMtVRYj9n6R7e9
als UDP-Datagramm an Port 34567, um den SSH-Dienst des Servers server.schoen-technisch.de freizugeben.
Integration in OpenSSH
Der Client von OpenSSH sieht nicht direkt die Integration von Befehlen vor, die lokal vor dem Verbindungsaufbau ausgeführt werden sollen. Es gibt jedoch zwei Funktionen, die sich für unsere Anwendung zweckentfremden lassen:
- Die Konfigurationsdirektive
Match
bietet das Kriterienschlüsselwortexec
, das eigentlich dazu dient, den Rückgabewert eines Befehls als Kriterium zu verwenden. Innerhalb des Befehls ist u.a. der Platzhalter%h
verfügbar, welcher zum Hostname des Servers expandiert. - Die Konfigurationsdirektive
Host
bietet die SubdirektiveProxyCommand
, mit dem sich ein Befehl definieren lässt, über den die SSH-Verbindung mittels stdin und stdout aufgebaut werden soll. Im Befehl stehen u.a. die Platzhalter%h
für den Hostname sowie%p
für den SSH-Port des Servers zur Verfügung. Da sich Befehle verketten lassen, können wir die im vorhergehenden Teil beschriebenen Verbindungsproxys verwenden, um in einem ersten Aufruf das UDP-Datagramm mit dem Credential abzusetzen, und mit einem zweiten Aufruf stdin/stdout mit einer TCP-Verbindung zum SSH-Dienst des Servers zu verbinden.
Konfigurationsdateien des OpenSSH-Clients
Alle hier beschriebenen Konfigurationsoptionen sollen in eine Konfigurationsdatei des OpenSSH-Clients aufgenommen werden. Der OpenSSH-Client greift dabei auf eine systemweite sowie eine benutzerspezifische Konfigurationsdatei zu.
Die systemweite Konfigurationsdatei findet sich bei Linux-Systemen üblicherweise unter /etc/ssh/ssh_config
, auf Windows-Systemen findet man sie unter %PROGRAMDATA%\ssh\ssh_config
. Die benutzerspezifische Konfigurationsdatei hingegen ist unter Linux mit dem Pfad ~/.ssh/config
bzw. unter Windows mit dem Pfad %USERPROFILE%\.ssh\config
zu lokalisieren.
Mit Ausnahme spezieller Mehrbenutzerumgebungen ist es empfehlenswert, die benutzerspezifische Konfigurationsdatei zu verwenden.
Alternativ ist es auch möglich, eine ganz andere Konfigurationsdatei zu verwenden, und diese beim SSH-Aufruf mit -F
anzugeben, oder aus einer der Standardkonfigurationsdateien heraus mittels Include
-Direktive aus einer anderen Datei zu laden. Dies ist beispielsweise sinnvoll, wenn die Datei mit den Credentials auf einem USB-Stick, in einem verschlüsselten Verzeichnis, o.ä. abgelegt werden soll.
Integration per Match exec
Diese Methode eignet sich besonders für Nutzer von Linux und anderen Unix- und Unix-artige OS, die die Bash-Shell nutzen bzw. deren System diese mitbringt, da wir so gänzlich ohne einen TCP-Proxy arbeiten können.
Mit der folgenden Konfiguration klopft OpenSSH bei einem Verbindungsaufbau zum Server server.schoen-technisch.de mit einem UDP-Datagramm mit dem Credential lduiSyvSwYZzwp9OfPiMtVRYj9n6R7e9
an Port 34567 an:
OpenSSH unterstützt interne Umschreibungen und Kanonisierung von Hostnames. Dies lässt sich beispielsweise auch für Aliase einsetzen. Das Kriterienschlüsselwort final
sorgt dafür, dass die Match
-Direktive nur im letzten Schritt der Auflösung des Hostnames zum Tragen kommt, denn anderenfalls würde ggf. mehrfach beim Server angeklopft.
Da es sinnvoll ist, für jeden Server ein eigenes Credential zu verwenden, wird die Match
-Direktive mittels eines host
-Kriteriums auf einen bestimmten Hostname beschränkt, bevor mit dem exec
-"Kriterium" das eigentliche Anklopfen durchgeführt wird.
Ab sofort sollte somit die Verbindung zum Server auch ohne manuelles Anklopfen funktionieren:
In Umgebungen, in denen Bash zwar auf dem System vorhanden, nicht aber die Standard-Shell des Nutzers ist, kann bash -c
verwendet werden, um den Befehl innerhalb einer Bash-Subshell auszuführen.
Alternativ können die im ersten Teil des Tutorials vorgestellten Tools OpenBSD netcat, Nmap ncat oder socat zur Übertragung des Credentials als UDP-Datagramm eingebunden werden.
Integration per ProxyCommand
Diese Methode eignet sich besonders für alle Fälle, in denen mit einem Anklopf-Tool gearbeitet werden soll, das gleichzeitig auch als TCP-Proxy verwendet werden kann, oder wenn eine Konfigurationsdatei bereits Host
-Knoten verwendet, um serverspezifische Einstellungen festzulegen.
Dabei werden in der Konfigurationsdatei zunächst Host
-Knoten je Server angelegt, der dann Subdirektiven hinzugefügt werden. Eine dieser Subdirektiven ist ProxyCommand
, die wir zweckentfremden. Sie ist eigentlich nur für die Anbindung eines Proxy-Tools für den Aufbau der SSH-Verbindung anstelle der ssh
-eigenen Netzwerkfunktionalität gedacht, sodass wir hier im Anschluss an das Anklopfen die Netzwerkverbindung zum Server über ein Tool als TCP-Proxy aufbauen müssen, welcher stdin und stdout per TCP mit dem SSH-Port des Servers verbindet. Der Anklopf- und der Proxy-Befehl werden dazu in den üblichen Linux-/Unix-Shells einfach mittels ;
getrennt – unter Windows ist es etwas weniger straight-forward, dazu später mehr. Der Platzhalter %h
wird dabei von OpenSSH durch den Hostname des Servers ersetzt, und %p
durch den SSH-Port.
OpenBSD netcat unter Linux/Unix
Der resultierende OpenSSH-Konfigurationseintrag für Portknocking und Proxying mittels OpenBSD netcat unter Linux/Unix sieht dann wie folgt aus:
Bei Bedarf kann der Host
-Knoten zusätzlich auch noch um weitere Konfigurationsoptionen (man 5 ssh_config
) ergänzt werden, bspw. User
zur Angabe des Benutzernamens auf dem Server, Port
zur Angabe eines abweichenden SSH-Ports, oder Hostname
zur Angabe des Server-Hostnames oder seiner IPv4- oder IPv6-Adresse. Die Angabe von Hostname
macht zum Beispiel dann Sinn, wenn man unter Host
statt des vollständigen Hostnamen des Servers lieber ein Kürzel oder anderen Alias verwenden möchte, sodass ein Verbindungsaufbau mittels ssh <kürzel>
möglich ist. Das selbe gilt, wenn man mit Hostname
eine IP-Adresse statt eines Hostnames verwenden möchte, um von funktionierender DNS-Auflösung unabhängig und vor möglichen DNS-basierten Angriffen geschützt zu sein.
Falls beim Aufbau aller SSH-Verbindungen unter Verwendung der SSH-Konfigurationsdatei ein Anklopfen am selben UDP-Port mit dem selben Credential stattfinden soll, kann statt Host <hostname|kürzel>
auch Host *
verwendet werden, um die folgenden Subdirektiven für alle Verbindungen zu setzen.
Nmap ncat unter Linux/Unix
Um Nmap ncat statt OpenBSD netcat als Anklopf-Tool und TCP-Proxy zu verwenden, sieht das Schnipsel in der SSH-Clientkonfiguration so aus:
socat unter Linux/Unix
Für socat hingegen kann ein Konfigurationsschnipsel dieser Art verwendet werden:
Bei socat ist darauf zu achten, dass bedingt durch die Verwendung von :
in der Adresssyntax die Verbindung zu IPv6-Adressen, unabhängig davon ob über der Befehlszeile angegeben oder mittels Hostname
-Subdirektive festgelegt, nicht ohne Anpassungen möglich ist. Soll direkt an einer IPv6-Adresse angeklopft werden, ist die Angabe des Platzhalters %h
in beiden Befehlen der Befehlskette durch [%h]
zu ersetzen.
Nmap ncat unter Windows
Seit Windows 10 Release 1803 ist OpenSSH fester und standardmäßig installierter Bestandteil von Windows. Allerdings ist die Erzeugung von UDP-Datagrammen und TCP-Proxying nur eher umständlich mit Bordmitteln wie Powershell zu bewerkstelligen. Ncat aus dem Windows-Build von Nmap ist jedoch (anders als diverse andere netcat-Portierungen für Windows) gut gepflegt und uneingeschränkt zu empfehlen.
Unter Linux/Unix ist das Wrapping des ProxyCommand-Befehls optional und nur bei Verwendung einer sehr speziellen Shell notwendig. Unter Windows wird der Befehl jedoch nicht mit einem Standard-Befehlsinterpreter ausgeführt, sodass das Wrapping notwendig ist – in unserem Fall mit cmd /c
, da dieser Befehlsinterpreter in allen Installationen verfügbar sein sollte.
Leider unterstützt der echo
-Befehl von cmd keinen Parameter, der das Line Feed (bzw. bei Windows Carriage Return + Line Feed) am Zeilenende unterdrückt, sodass man sich eines Hacks bedienen muss; wir setzen mit set /p
eine anonyme Variable und geben ihren Inhalt aus, dabei muss zur Bestätigung an set
entweder ein Line Feed an stdin übergeben, oder stdin durch ein Pipe-In von NUL geschlossen werden. Das Verketten den Portknocking-Befehls und TCP-Proxying-Befehls erfolgt bei cmd mittels &
.
Einschränkung bei Verwendung von ProxyCommand
Die Integration des Anklopf-Befehls in die SSH-Konfiguration geht einher mit einer kleinen Einschränkung: Die SSH-Parameter -4
und -6
bzw. die OpenSSH-Konfigurationsoption AddressFamily
zur Festlegung der Adressfamilie für den Verbindungsaufbau auf IPv4 oder IPv6 zeigt keine Wirkung mehr, da die Namensauflösung des Hostnames und die Auswahl der Adressfamilie nun vom verwendeten Proxy-Tool abhängt, und nicht mehr von SSH. Dies lässt sich auf zweierlei Art umgehen:
- Möglichkeit 1: Das verwendete Anklopf-/Proxy-Tool selbst wird angewiesen, das Datagramm bzw. die Verbindung per IPv4 oder IPv6 zu senden/aufzubauen. Bei allen drei hier vorgestellten Tools (
netcat
,ncat
undsocat
) wird dies über die Befehlszeilenparameter-4
bzw.-6
gelöst. - Möglichkeit 2: Die Verbindung wird explizit zur IPv4- oder IPv6-Adresse des Servers aufgebaut. Dabei kann man die jeweilige IP-Adresse auch der
Host
-Sektion des Servers alsHostname
-Direktive hinzufügen.
Nur bei Verwendung von socat ist dabei auf die im zugehörigen Abschnitt oben beschriebene Einschränkung bei der Angabe von IPv6-Adressen zu berücksichtigen.
Integration in PuTTY
Der beliebteste grafische SSH-Client ist PuTTY, und auch mit diesem Client lässt sich die Proxy-Funktion analog zu OpenSSH für unser Portknocking zweckentfremden.
Dazu wird in der Kategorie Connection ➔ Proxy unter Proxy type der Modus Local (run a subcommand to connect) ausgewählt und unter Command to send to proxy (for some types) dann unser aus den vorhergehenden OpenSSH-Beispielen bekannter ProxyCommand-Befehl eingetragen, welchen wir noch wie folgt anpassen müssen:
Den Hostname des SSH-Servers ersetzt PuTTY im Proxy-Befehl mit dem Platzhalter %host
statt %h
, den Port mit dem Platzhalter %port
statt %p
. Zusätzlich kennt PuTTY aber auch weitere Eingabefelder mit eigenen Platzhaltern, hier bspw. Proxy hostname und Port, welche wir für unser Credential und den für das Anklopfen zu verwendenden UDP-Port verwenden können. Auf diese Daten können wir über die Platzhalter %proxyhost
und %proxyport
zugreifen.
Über das Sitzungsmanagement von PuTTY können die Einstellungen dann gespeichert werden, entweder als Standardeinstellungen, oder (sinnvollerweise ergänzt um Hostname des Servers und ggf. Benutzername und Port) als serverspezifische Sitzung.
OpenBSD netcat unter Linux/Unix
Der resultierende Befehl mit OpenBSD netcat für Unix/Linux sieht dann so aus:
Die Befehlsketten mit Nmap ncat und socat aus den o.s. ProxyCommand-Direktiven für OpenSSH lassen sich bei Bedarf analog umformen.
Für unseren Beispielserver ergibt sich dann für PuTTY unter Linux/Unix diese Konfiguration:
Nmap ncat unter Windows
Analog zur Umschreibung des OpenBSD-netcat-ProxyCommands für PuTTY unter Linux/Unix sieht das für PuTTY angepasste ProxyCommand für Nmap ncat unter Windows entsprechend so aus:
Das Ergebnis für unseren Beispielserver mit PuTTY unter Windows ist das folgende:
Rück- und Ausblick: Credential in andere Protokolle integrieren
In diesem zweiten Teil des Tutorials haben wir nun erarbeitet, wie einfach sich unser Credential-Portknocking in SSH-Clients integrieren lässt, selbst wenn diese eigentlich keine Funktion dafür vorsehen, und können uns nun trotz Portknocking wieder wie gewohnt ohne Zusatzschritte bei unserem Server anmelden.
Im dritten und letzten Teil der Serie werden wir lernen, wie das des Credentials sich unauffällig auch in Datagramme anderer Protokolle wie ICMP Echo Requests (Ping) oder SNMP-Abfragen integrieren lässt. Auf diese Weise lässt sich das Anklopfen unauffälliger gestalten und eventuell vorgeschaltete Firewalls ohne zusätzliche Portfreigabe durchqueren.