Various methods of tunneling ssh connections to pierce through restrictive firewalls.
The following setups are evaluated:
- HTTPTunnel+stunnel4, moderately difficult to setup, but once
installed it appears as legitimate HTTPS traffic.
- Iodine, setup needs the most effort, however when done and the
network allows DNS queries it works quite reliably.
- CurveCP, setup is quite easy, when done the link is encrypted and
fast. However the firewalls that allow UDP/53 to pass are somewhat
limited.
- ICMPTX, setup is quite easy, however there is no encryption, use it
only to tunnel encrypted traffic like ssh and such.
- TOR, setup is easy, usage is a bit delayed due to the latency of
the Tor network, requests look like normal HTTPS traffic.
The setup with the most effort seems to be also the most reliable, an
iodine based link over DNS can break out of a lot of networks. If we
can use HTTP to browse but other services are restricted then the
httptunnel is adequate. For less setup-hassle but increased latency
Tor tunnels also deliver reliably. The usefulness of ICMP and CurveCP
tunnels depend on the firewall configuration, but if they work,
they're pretty fast.
Over HTTPS
This method is generally useful in heavily restricted networks, where
you can only use the web for browsing, but no other services are
allowed.
We use the fine tool httptunnel for masking our ssh connection.
However httptunnel is not encrypted, and thus also the ssh
handshake can be identified in the traffic. To avoid that, we
put a tunnel into our tunnel using stunnel.
On the server
First we need to generate the SSL certificate:
openssl req -new -x509 -days 365 -nodes \
-out htcert.pem -keyout htcert.pem
Set up an stunnel, make sure to set the ip address, and the user
and group exist:
/usr/bin/stunnel -f -r 127.0.0.1:8888 \
-d <public ip address>:443 \
-p htcert.pem -s stunnel4 \
-g stunnel4 -P ''
When this is done, we can run httptunnel to connect the sshd with
the stunnel:
/usr/bin/hts -w -F 127.0.0.1:22 127.0.0.1:8888
On the client
Get the generated certificate from the server (don't forget to remove
the private key part). You need to rename the cert to it's hash value
and append a '.0':
mv htcert.pem $(openssl x509 -noout -hash -in htcert.pem).0
Now start the stunnel:
sudo stunnel -c -d 127.0.0.1:8888 \
-r <server-address>:443 \
-s stunnel4 -g stunnel4 -P '' -a . -v 3
We need to set the server address (can be IP or name-based), make
sure the user, group exist.
start the httptunnel:
htc -F <httptunnel-port> 127.0.0.1:8888
The tunnel will listen on httptunnel-port.
Enjoy your ssh-over-https:
ssh -p <httptunnel-port> 127.0.0.1
Over DNS
In some cases internet access is blocked however DNS traffic is
allowed to pass, allowing us to tunnel through DNS.
If you can setup a special DNS entry for this, tunneling through DNS
is very easy using the excellent iodine tool. Follow the
straight-forward installation instructions.
Use this method if the network allows resolving of names, even if a
local DNS server is forced on us, the tunnel will still work due to
recursive queries hitting your "authoritative server".
Hint: you can manage and delegate a DNS zone for free on affraid.org,
if you don't have your own.
Using CurveCP on UDP/53
The drawbacks of using any DNS protocol based tunnel like iodine, are
that the tunnel has size-wise a huge protocol overhead, you need to
setup a slightly uncommon DNS configuration and domain names you
control are usually registered on your real name. If the firewall does
not force the usage of a local DNS server and it allows traffic to
UDP/53, then CurveCP tunnel is a preferred option.
Alternatively you could also run on UDP/80 or other allowed UDP ports.
On the server
Note: During testing I had to recompile CurveCP as the address family
was missing from the bind call, see the patch at the end of this post.
Create a server key:
curvecpmakekey serverkey
convert the key to hex, and store it on the client in serverkey.hex:
curvecpprintkey serverkey > serverkey.hex
run the curvecpserver:
curvecpserver <your host name> \
serverkey \
<your ip address> \
53 \
00000000000000000000000000000000 \
curvecpmessage /usr/sbin/sshd -i
On the client
This depends on socat the excellent swiss-army knife of socket
handling.
Store the serverkey.hex that you generated on the server and run the
client:
curvecpclient <curvecpserver hostname> \
$(cat serverkey.hex) \
<curvecpserver ip address> \
53 \
00000000000000000000000000000000 \
curvecpmessage \
-C sh -c "/usr/bin/socat tcp4-listen:9999,bind=127.0.0.1,reuseaddr,fork - <&6 >&7"
Start your ssh-over-curvecp:
ssh -p 9999 127.0.0.1
Over ICMP
Using ICMPTX you can set up tun devices that tunnel over ICMP, which
is quite handy as in some cases it's not filtered and allows to pierce
through the blockades. ICMPTX creates a local network device, so
tunneling anything is quite easy after setup.
On the server
Simply run
(sleep 1; ifconfig tun0 10.0.99.1 netmask 255.255.255.0 )&; icmptx -s <server ip address>
On the client
Simply run
(sleep 1; ifconfig tun0 10.0.99.2 netmask 255.255.255.0 )&; icmptx -c <server ip address>
sshing to your box is then a simple:
ssh 10.0.99.1
Using Tor
Tor is great for hiding traffic, it's latency is a bit bigger than
usual, but it's quite possible to get work done through tor tunnels
even with ssh. If you
configure your client-side tor proxy to use a tor bridge that runs on
port 443, then the tunnel looks like casual HTTPS traffic.
There's two options, you can connect from a tor exit node to your
normal ssh server, in this case skip the "On the server" part, and
use your normal hostname instead of the .onion address referenced
there.
On the server
If you want to run your ssh tunnel as a tor hidden service you
simply have to add the following two lines
HiddenServiceDir /var/lib/tor/sshtun/
HiddenServicePort 22 127.0.0.1:22
to your /etc/tor/torrc. And find out the hostname of your new
hidden service with:
cat /var/lib/tor/sshtun/
On the client
You simply need to call the torified ssh:
torify ssh <.onion hostname from server>
Code
Stubs for running the server-side daemons using the excellent runit
tool can be found on github. These can be most easily installed using
deamonize.sh. For client-side setup use the instructions in this post.
curvecp patch
curvecpserver had to be patched, as the address family in the bind
call was uninitialized, the patch is below:
diff -urw nacl-20110221/curvecp/socket_bind.c nacl-20110221-new/curvecp/socket_bind.c
--- nacl-20110221/curvecp/socket_bind.c 2011-02-21 02:49:34.000000000 +0100
+++ nacl-20110221-new/curvecp/socket_bind.c 2012-08-19 02:52:25.000000000 +0200
@@ -9,6 +9,7 @@
{
struct sockaddr_in sa;
byte_zero(&sa,sizeof sa);
+ sa.sin_family = AF_INET;
byte_copy(&sa.sin_addr,4,ip);
byte_copy(&sa.sin_port,2,port);
return bind(fd,(struct sockaddr *) &sa,sizeof sa);