Firewall Piercing/Beispiele/OpenSSH via HTTPS-Proxy als SOCKS Proxy verwenden

Aus UUGRN

Dieses Beispiel beschreibt den Ausbruch aus einem System, welches nur via HTTPS-Proxy auf das freie Internet zugreifen kann. Darüber hinaus soll die initiale SSH-Verbindung gleichzeitig einen SOCKS-Proxy anbieten, der dann für weitere SSH-Sessions verwendet werden kann.

Voraussetzungen für dieses Beispiel

Das Rhein-Neckar-Wiki läuft in einem Jail auf dem Server der Stadtwiki Gesellschaft e.V. (rnw.stwserv.de). Die Wiki-Jails sind aus Sicherheitsgründen per Firewall von extern nur auf 80/tcp und 443/tcp erreichbar und können selbst nur über einen Proxy-Server (relay.stwserv.de) mit der Aussenwelt kommunizieren. Der Proxy-Server ist ein HTTP-Proxy und erlaubt zusätzlich die für HTTPS erforderliche CONNECT-Methode auf Port 443/tcp (https)

Ferner existiert ein Shell-Account auf shell.uugrn.org. Der dortige OpenSSH-Daemon läuft zusätzlich zum Standardport 22/tcp auch auf 443/tcp.

Es soll zu Demonstrationszwecken gezeigt werden, wie man unter diesen Voraussetzungen mit OpenSSH und BSD-netcat als Hilfstool eine SSH-Verbidung auf einen mehr oder weniger beliebigen Server im Internet herstellen kann, sofern man dort grundsätzlich Login-Rechte hat.

SSH-Client Konfiguration

zumächst legen wir volgende ~/.ssh/config an (oder ergänzen die vorhandene entsprechend):

[rabe@rnw ~]$ cat .ssh/config 
Host shell443
        HostName shell.uugrn.org
        Port 443
        User rabe
        TCPKeepAlive yes
        DynamicForward 127.0.0.1:1080
        ProxyCommand /usr/bin/nc -X connect -x relay.stwserv.de:3128 %h %p


Host rabe.socks
        Hostname rabe.uugrn.org
        Port 22
        User rabe 
        ProxyCommand /usr/bin/nc -X 5 -x 127.0.0.1:1080 %h %p

Das Profil ssh443 ist so angelegt, dass der Benutzer rabe sich auf shell.uugrn.org auf Port 443/tcp einloggt. Dabei wird mit dem ProxyCommand eine HTTPS-Proxy-Connection über relay.stwserv.de:3128 aufgebaut. Die so aufgebaute SSH-Session soll ausserdem einen SOCKS-Proxy auf 127.0.0.1:1080 bereitstellen.

Das Profil rabe.socks ist so angelegt, dass es auf rabe@rabe.uugrn.org:22 einloggt unter Verwendung des SOCKS(5)-Proxys auf 127.0.0.1:1080.


Verbindungsaufbau

nach shell.uugrn.org
443 via HTTPS-Proxy:
[rabe@rnw ~]$ ssh -N -f shell443 
The authenticity of host '[shell.uugrn.org]:443 (<no hostip for proxy command>)' can't be established.
DSA key fingerprint is c4:2e:90:bc:76:b7:ca:6e:69:fd:54:c8:21:4c:97:b8.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[shell.uugrn.org]:443' (DSA) to the list of known hosts.
Password:
[rabe@rnw ~]$

Durch die Optionen -N und -f sorgen wir dafür, dass nach dem Verbindungsaufbau auf dem Zielsystem kein Kommando ausgeführt wird und sich der SSH-Client als Daemon weg-forkt. Wir landen daher wieder auf unserer lokalen Shell.

nach rabe.uugrn.org
22 via SOCKS-Proxy:
[rabe@rnw ~]$ ssh rabe.socks 
The authenticity of host 'rabe.uugrn.org (<no hostip for proxy command>)' can't be established.
DSA key fingerprint is 5b:13:fa:16:78:93:be:e9:5c:84:33:d3:63:b6:c1:9f.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'rabe.uugrn.org' (DSA) to the list of known hosts.
Password:

[rabe@rabe ~]$ 

Dieser Verbindungsaufbau liefert uns eine normale, interaktive Shell auf rabe.uugrn.org.

Spuren
  • Im Proxy-Logfile auf relay.uugrn.org erscheint hier lediglich ein CONNECT auf shell.uugrn.org:443 und die Anzahl der übertragenen Bytes. Allerdings erst, wenn die Verbindung zu shell.uugrn.org:443 abgebaut wird:
    1314483856.709 1579602 195.49.138.74 TCP_MISS/200 26199 CONNECT shell.uugrn.org:443 - DIRECT/195.49.138.100 -
  • Aus Sicht von shell.uugrn.org erfolgt der SSH-Login von relay.stwserv.de aus, d.h. es ist nicht sichtbar, dass der SSH-Client in Wirklichkeit auf rnw.stwserv.de läuft.
  • Aus Sicht von rabe.uugrn.org erfolgt der SSH-Login von shell.uugrn.org aus. Hier ist nichteinmal erkennbar, dass der Client ganz woanders läuft.


Ad-hoc

Man will möglicherweise nicht für jeden einzelnen Zielrechner, auf den man per SSH ausbrechen will erst in der ~/.ssh/config anlegen. Die erforderlichen Direktiven kann man auch mit -o 'Direktive Parameter' als Kommandozeilenparameter für den OpenSSH-Client angeben.

Beispiel

Es soll Ad-hoc unter Verwendung des SOCKS-Proxys auf sigsys.de zugegriffen werden und dort 'hostname ; echo $SSH_CLIENT' ausgeführt werden:

[rabe@rnw ~]$ ssh -o 'ProxyCommand /usr/bin/nc -X 5 -x 127.0.0.1:1080 %h %p' rabe@sigsys.de 'hostname ; echo $SSH_CLIENT'
The authenticity of host 'sigsys.de (<no hostip for proxy command>)' can't be established.
DSA key fingerprint is df:ff:2a:d7:af:4e:99:44:ad:0d:9f:81:28:96:83:de.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'sigsys.de' (DSA) to the list of known hosts.
Password:
sigsys.de
195.49.138.100 55726 22
Zu sehen
der Login erfolgt tatsächlich auf sigsys.de und laut $SSH_CLIENT kommt die SSH-Verbindung auch via shell.uugrn.org (195.49.138.100) rein.
Nicht zu sehen
Es gibt es keinen Rückschluss auf rnw.stwserv.de oder relay.stwserv.de bzw. dass der SSH Client über einen SOCKS-Proxy via shell.uugrn.org reinkommt.

SOCKS Proxy funktioniert nicht

Versucht man einen nicht mehr vorhandenen SOCKS-Proxy zu verwenden, erhält man folgende Meldung:

ssh_exchange_identification: Connection closed by remote host

Schaut man genauer hin, findet man leider keine genaueren Infos, insbesondere nicht, dass netcat (nc) ein Problem hat:

[rabe@rnw ~]$ ssh -vvv -o 'ProxyCommand /usr/bin/nc -X 5 -x 127.0.0.1:1080 %h %p' rabe@sigsys.de 'hostname ; echo $SSH_CLIENT' 
OpenSSH_5.1p1 FreeBSD-20080901, OpenSSL 0.9.8e 23 Feb 2007
debug1: Reading configuration data /home/rabe/.ssh/config
debug1: Reading configuration data /etc/ssh/ssh_config
debug2: ssh_connect: needpriv 0
debug1: Executing proxy command: exec /usr/bin/nc -X 5 -x 127.0.0.1:1080 sigsys.de 22
debug1: permanently_drop_suid: 1001
debug1: identity file /home/rabe/.ssh/identity type -1
debug1: identity file /home/rabe/.ssh/id_rsa type -1
debug1: identity file /home/rabe/.ssh/id_dsa type -1
ssh_exchange_identification: Connection closed by remote host


Idle-Timeout

Im gezeigten Setup wird der HTTPS-Proxy die Verbindung zwangsweise nach einer gewissen Zeit trennen, wenn keine Daten übertragen wurden (idle timeout). Das betrifft primär die Verbindung zu shell.uugrn.org:443, die als SOCKS-Proxy fungiert. Die Direktive TCPKeepAlive yes sorgt hier nicht dafür, dass Payload-Daten übertragen werden, entsprechend wird eine andere Methode benötigt.

Mit ServerAliveInterval und ServerAliveCountMax veranlasst man den OpenSSH Client, regelmäßig Alive-Messages mit dem OpenSSH Server auszutauschen. Hierbei werden innerhalb der SSH-Verbindung Daten übertragen. Wenn mehr als ServerAliveCountMax Messages fehlschlagen, erklärt der SSH-Client die Verbindung für abgebrochen und beendet sich.

Wir nutzen dieses Feature, um regelmäßig Payload-Daten zu übertragen und somit dem Idle-Timeout durch den HTTPS-Proxy vorzubeugen.

ssh_config(5)
     ServerAliveCountMax
             Sets the number of server alive messages (see below) which may be
             sent without ssh(1) receiving any messages back from the server.
             If this threshold is reached while server alive messages are
             being sent, ssh will disconnect from the server, terminating the
             session.  It is important to note that the use of server alive
             messages is very different from TCPKeepAlive (below).  The server
             alive messages are sent through the encrypted channel and there-
             fore will not be spoofable.  The TCP keepalive option enabled by
             TCPKeepAlive is spoofable.  The server alive mechanism is valu-
             able when the client or server depend on knowing when a connec-
             tion has become inactive.

             The default value is 3.  If, for example, ServerAliveInterval
             (see below) is set to 15 and ServerAliveCountMax is left at the
             default, if the server becomes unresponsive, ssh will disconnect
             after approximately 45 seconds.  This option applies to protocol
             version 2 only.

     ServerAliveInterval
             Sets a timeout interval in seconds after which if no data has
             been received from the server, ssh(1) will send a message through
             the encrypted channel to request a response from the server.  The
             default is 0, indicating that these messages will not be sent to
             the server.  This option applies to protocol version 2 only.

Der Standardwert für ServerAliveCountMax 3 ist für unseren Anwendungsfall ausreichend, als ServerAliveInterval benötigen wir einen Wert, der definitiv geringer ist als der Timeout vom HTTPS-Proxy. Auf der sicheren Seite sind wir bei 5 min oder 300 sec.

Die Datei ~/.ssh/config sollte daher mindestens um ServerAliveInterval 300 ergänzt werden:

Host shell443
        HostName shell.uugrn.org
        Port 443
        User rabe
        TCPKeepAlive yes
        ServerAliveInterval 300
        DynamicForward 127.0.0.1:1080
        ProxyCommand /usr/bin/nc -X connect -x relay.stwserv.de:3128 %h %p
Nebenbei bemerkt
Manche Router mit Stateful Firewall oder NAT vergessen bei inaktiven SSH-Sessions die TCP Verbindungsparameter. Hiergegen hilft manchmal TCPKeepAlive yes. Manchmal genügt das allerdings nicht, weil kein Payload übertragen wird. Hier hilft dann normalerweise ServerAliveInterval 30 recht zuverlässig. Braucht man sehr kurze Intervalle, können SSH-Sessions bei temporärem Netzausfall (zB 90 sec) verloren gehen. Hier sollte man dann zusätzlich ServerAliveCountMax 10 oder noch höher setzen, je nachdem, wie lange man einen temporären Netzausfall tolerieren möchte (z.B. Funkloch bei UMTS).


Netzwerk

Nachfolgend ist der Traffic zwischen relay.stwserv.de:54677 und shell.uugrn.org:443 mit tcpdump dokumentiert, wenn sonst keine Daten übertragen werden (Idle):

Der erste Block zeigt den Verbindungsaufbau nach shell.uugrn.org:443:

01:08:00.077789 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 0
01:08:00.078243 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 0
01:08:00.078273 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 0
01:08:00.107722 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 40
01:08:00.108161 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 40
01:08:00.110269 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 776
01:08:00.110492 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 792
01:08:00.209650 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 24
01:08:00.209991 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 0
01:08:00.214078 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 152
01:08:00.218946 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 144
01:08:00.231205 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 1040
01:08:00.247505 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 16
01:08:00.346624 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 48
01:08:00.347045 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 0
01:08:00.347159 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 48
01:08:00.347426 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 64
01:08:00.352175 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 64
01:08:00.352478 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 96
01:08:00.356646 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 64
01:08:00.456532 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 0
01:08:02.480069 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 80
01:08:02.483309 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 48
01:08:02.483537 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 80
01:08:02.484434 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 32
01:08:02.485710 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 64
01:08:02.585450 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 0

Der zweite Block zeigt, dass alle 30sec (für ServerAliveInterval 30!) seit dem Verbindungsaufbau kleine Datenmengen (Payload) übertragen werden:

01:08:32.482686 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 64
01:08:32.483463 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 32
01:08:32.583427 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 0

01:09:02.480781 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 64
01:09:02.481610 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 32
01:09:02.581590 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 0

Der dritte Block zeigt, wenn die SSH-Session abgebrochen wird, zum Beispiel, indem der ssh-Prozess gekillt wird:

01:09:29.937978 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 0
01:09:29.938752 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 0
01:09:29.938898 IP 195.49.138.100.443 > 195.49.138.67.54677: tcp 0
01:09:29.938917 IP 195.49.138.67.54677 > 195.49.138.100.443: tcp 0