Up one level
SSH Tunneling (TCP port forwarding)
Spencer Stirling

tip: April 2009. I have found that the tunnels "collapse" (i.e. don't die, but no longer function properly) if the option "TCPKeepAlive yes" is used in the /etc/ssh/sshd_config file. Instead I set this option to "no" and add the lines "ClientAliveCountMax 3" and "ClientAliveInterval 15". Check the manpages for sshd_config for details.

SSH Tunneling is probably one of the coolest things that you can learn how to do. Basically, an SSH tunnel is an internet "pipeline" through which data can be sent. If used appropriately then your data is always encrypted, which prevents eavesdropping. But that's NOT ALL!!! SSH tunnels can also be used to connect machines on opposite sites of a firewall(s). More precisely, an SSH tunnel forwards a *local* TCP port to a *remote* TCP port through a firewall!!! This can be useful in any of the following situations:

1) You need to "talk directly" (to a specific TCP port) to a machine that is separated from you by a firewall/gateway (assuming that you can *at least* SSH into the gateway).

2) You can send data directly to a machine, but you don't like the fact that the data is sent unencrypted.

3) Both (1) and (2).

First application: Bypassing a Gateway
Consider the following situation: you are sitting on "mymachine", and you have a program that needs to send data to a TCP port (say 5900) on "remotemachine". If the two machines can talk directly then there is no problem, but what if "remotemachine" is behind "firewall"? In that case you cannot even refer to "remotemachine" directly - as far the outside world is concerned "remotemachine" doesn't even exist. All communication between "remotemachine" and the internet is done through "firewall".

If you *happen* to be able to SSH into the "firewall", however, then you are in luck. In that case, "firewall" knows about "remotemachine", and "mymachine" knows about "firewall", so maybe a menage a trois can be rigged up. The following SSH command (run on "mymachine") sets up a tunnel:

ssh -N -L 33642:remotemachine:5900 user@firewall

Then you could telnet (for example) to "remotemachine" port 5900 by running the command on "mymachine"

telnet localhost 33642

Let's dissect this a little. The main piece is "ssh user@firewall" - you are literally SSHing into the firewall. So when it asks for a password, give the one for your account on the firewall. The "-N" is not important - this just keeps the pipe open.

The "-L 33642:remotemachine:5900" piece is the RELAY part. This tells SSH that you don't want to actually OPEN UP A SHELL on firewall, but instead that you are merely using it as a relay. Notice that "remotemachine" doesn't necessarily need to be addressable from the outside - it is only necessary that "firewall" knows how to find it!!! The piece "-L" tells SSH to listen to port 33642 on "mymachine" (listen locally) and redirect any received data THROUGH "firewall" and on to port 5900 on "remotemachine". So, in essence, telnet'ing to "localhost 33642" is *like* telnet'ing to "remotemachine 5900".

There are a few things to note here:

1) port 33642 was chosen at random. You merely need a free port on "mymachine" to use and abuse (try "netstat" to list ports in use).

2) The session is encrypted between "mymachine" and "firewall", but in between "firewall" and "remotemachine" the data is sent IN THE CLEAR (i.e. as usual), so be aware (there is a way to fix this - see below).

3) It IS possible to "chain" up SSH tunnels to go through MULTIPLE gateways if necessary!

4) There is a "-R" switch that does the OPPOSITE - it captures traffic on the remote side and forwards it to the local machine.

Now that we know how to go through one firewall, let's learn how to chain up SSH tunnels to punch through multiple firewall levels! So I want to implement the diagram:

mymachine ---> Afirewall ---> Bfirewall ---> remotemachine

This is easy, as long as you have SSH accounts on both firewalls! Let me mention here that this is the "lazy man's" way - meaning that data is encrypted all of the way through EXCEPT (as usual) at the last level between "Bfirewall" and "remotemachine". Again, see below for a way to remedy this.

First, SSH into "Afirewall" (like you've ALWAYS known how to do, i.e. log in to a shell) and run "netstat" to find an open port there (say 4652 for our example). Then, while logged into "Afirewall" set up the last "hop" with the command:

ssh -N -L 4652:remotemachine:5900 user@Bfirewall

This command picks up port 4652 on "Afirewall" and forwards it THROUGH "Bfirewall" to its final destination at "remotemachine" port 5900.

Now, back on "mymachine" execute

ssh -N -L 33642:localhost:4652 user@Afirewall

This opens up a tunnel from port 33642 on "mymachine" and forwards THROUGH "Afirewall" to ITSELF on port 4652 (remember... when using the -L option the machine name that appears between the :: is resolved from the point-of-view of "Afirewall", so localhost is ITSELF).

You can obviously go through any number of firewalls this way - just run an SSH tunnel between each firewall IN TURN, always forwarding to the relaying firewall ITSELF except at the last "hop" (however, see below for greater security).

Using an SSH Tunnel to encrypt your session
SSH tunnels are not only useful for punching through firewalls, but they are also useful if you want an encrypted channel from source to destination. Let's suppose that you can talk directly to "remotemachine", then you could just directly

telnet remotemachine 5900

But you MIGHT not want to do that! You data goes over the internet in the open! There is a better way, that is if you have to ability to SSH in "remotemachine". If so then consider these commands (run on "mymachine"):

ssh -N -L 33642:localhost:5900 user@remotemachine
telnet localhost 33642

The first command creates an encrypted pipe from "mymachine" to "remotemachine" that - guess what - forwards data to ITSELF (see how the word "localhost" shows up between :: - that's "remotemachine" referring to ITSELF)! The second command opens up a telnet session on "mymachine" to port 33642. This session is then FORWARDED over the pipe to "remotemachine", which then forwards the session to ITSELF on port 5900! Pretty cool, huh? So your data is not running around in the open. Obviously, when SSH asks for a login here you should give your login information for "remotemachine".

You can combine this technique while you are punching through firewalls, too! The strategy is this: follow the technique for forwarding through multiple firewalls as outlined above, but at the last firewall don't "wimp out" and just "hop" over it. Instead, use the "forwarding to itself" method at EVERY step (this will require one extra step to the final "remotemachine").

Let's see how this goes: first, get an SSH *shell* session on each firewall (even the last one) going - probably by SSHing through each firewall successively. Find yourself a free port on each firewall. For simplicity I'll use an example with 4 firewalls, and the free ports will be 4651, 4652, 4653, and 4654 on each firewall, respectively (they could be the SAME numbers... obviously ports on different machines have nothing to do with each other). Then consider the sequence:

1) On "Dfirewall" run:
ssh -N -L 4654:localhost:5900 user@remotemachine

This sets up that final secure link between "Dfirewall" and "remotemachine" - using the technique that I just described, i.e. "remotemachine" forwards the final bit of traffic to itself, so there is NEVER a time when the data is out in the open.

2) On "Cfirewall" run:
ssh -N -L 4653:localhost:4654 user@Dfirewall

3) On "Bfirewall" run:
ssh -N -L 4652:localhost:4653 user@Cfirewall

4) On "Afirewall" run:
ssh -N -L 4651:localhost:4652 user@Bfirewall

5) On "mymachine" run:
ssh -N -L 33642:localhost:4651 user@Afirewall

That's IT! Now, on "mymachine" you could telnet into "remotemachine" with the command

telnet localhost 33642

Important TIP: in all of these examples I've been using "telnet", but really any program that needs to send to a port works. There IS ONE caveat! What if you forward to port 22 and try to use SSH as your generic "program"! In other words, after setting up the tunnel to port 22, you could *try* to SSH to "remotemachine" with the command

ssh -p 33642 localhost

Maybe this seems like a stupid thing to do (setting up an SSH tunnel just to get an SSH session), but many programs use SSH under the hood, so we'd better talk about it. The problem is that SSH will give you the very scary "WATCH OUT PEOPLE ARE WATCHING YOU" and "MAN-IN-THE-MIDDLE ATTACK" messages - and probably won't let you proceed. This is because SSH keeps a certificate around from every machine to which it connects. If this certificate ever changes then SSH assumes that something went wrong. So, in the above example, SSH looks up the certificate for "localhost" - which will definitely not match the certificate that it's *actually getting* from "remotemachine" over the SSH tunnel. The way around this is to use the MODIFIED SSH command

ssh -p 33642 -o HostKeyAlias="remotemachine" localhost

This will tell SSH to not check the "localhost" certificate, but rather that for "remotemachine".

If you are going to hook up with sketchy women than you should Double-Bag IT!!!
There is one obvious point of weakness here - what if you don't FULLY trust those firewalls? It would be relatively easy for a user on any firewall to sniff your traffic (and modify IT, if they want!!!). This is because as traffic comes into the firewall it is decrypted. Then it is sent to the appropriate port, where ANOTHER copy of SSH picks it up, encrypts it again, and sends it on to the next firewall.

The solution is to run another SSH tunnel INSIDE of the SSH tunnel. The price is this: you'll have to use up another port on your local machine (but who cares?). So, using the previous example (with 4 firewalls) you should execute:

1) On "Dfirewall" run:
ssh -N -L 4654:localhost:22 user@remotemachine

Notice the main difference HERE from the previous example. I am forwarding to port 22 (the SSH port) instead of the final destination port 5900! This is so that I can (later) set up an SSH tunnel within this SSH tunnel.

2) On "Cfirewall" run:
ssh -N -L 4653:localhost:4654 user@Dfirewall

3) On "Bfirewall" run:
ssh -N -L 4652:localhost:4653 user@Cfirewall

4) On "Afirewall" run:
ssh -N -L 4651:localhost:4652 user@Bfirewall

5) On "mymachine" run:
ssh -N -L 43642:localhost:4651 user@Afirewall

At this point we have an SSH tunnel from port 43642 (ANOTHER random port that was chosen) to the SSH server listening on port 22 on "remotemachine".

Now, to set up the "tunnel within the tunnel" I could execute the following command on "mymachine":

ssh -p 43642 -N -L 33642:localhost:5900 -o HostKeyAlias="remotemachine" localhost

This sets up the tunnel from port 33642 on "mymachine" to port 5900 on "remotemachine" INSIDE the previous tunnel that had been established from port 43642 on "mymachine" to port 22 on "remotemachine".

The "-o HostKeyAlias=" statement is VERY important. See the discussion above for an explanation.

An example where the "-R" switch can be very useful
Now let's suppose that you DON'T have an SSH account on the firewall! What can you do? You still want to forward port 33642 on "mymachine" to port "5900" on "remotemachine". The reason that we're dealing with any of this at all is that "remotemachine" is hidden behind a firewall. However, "mymachine" is not (or, at least, we'll assume for simplicity that "mymachine" is public). So "remotemachine" can actually see "mymachine", just not the other way around.

If you can ever get PHYSICAL access to "remotemachine" then you're in luck (say, you go into work every day). On "remotemachine" you could run the following command:

ssh -N -R 33642:localhost:5900 "mymachine"

That sets up a tunnel that forwards all traffic from the REMOTE port (in this case "mymachine" port 33642) to port 5900 on localhost (i.e. "remotemachine"). After running this at work you could then set up whatever program to listen to port 5900 and go home.

Now, at home on "mymachine" it would be easy to telnet into "remotemachine" port 5900. Just do the usual

telnet localhost 33642

Now THAT'S COOL! Consider all of the neat things you can do with this. Personally I'd set up a forward to port 22 - then you can have full SSH access from home and set anything else up that you could ever want remotely!

This page has been visited   times since March 26, 2006