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
8,606 (14 today) times since March 26, 2006
--
map{ map{tr|10|# |;print} split//,sprintf"%.8b\n",$_}
unpack'C*',unpack'u*',"5`#8<3'X`'#8^-@`<-CPP`#8V/C8`"