SSH Port Forwarding

Machine Translations

Introduction

SSH is typically used for logging into remote servers so you have shell access to do maintenance, read your email, restart services, or whatever administration you require. SSH also offers some other native services, such as file copy (using scp and sftp) and remote command execution (using ssh with a command on the command line after the hostname).

Whenever we SSH from one machine to another, we establish a secure encrypted session. This first article in this SSH series[1] looked at properly verifying a server's host key, so that we can be sure that no attacker is able to perform a man-in-the-middle attack and gain access to read or manipulate what we do in that session. Other articles in this series looked at removing the need for static passwords using SSH user identities[2], and then using ssh-agent[3] to automate the task of typing passphrases.

SSH also has a wonderful feature called SSH Port Forwarding, sometimes called SSH Tunneling, which allows you to establish a secure SSH session and then tunnel arbitrary TCP connections through it. Tunnels can be created at any time, with almost no effort and no programming, which makes them very appealing. In this article we look at SSH Port Forwarding in detail, as it is a very useful but often misunderstood technology. SSH Port Forwarding can be used for secure communications in a myriad of different ways. Let's start with an example.

LocalForward Example

Say you have a mail client on your desktop, and currently use it to get your email from your mail server via POP, the Post Office Protocol, on port 110.[4] You may want to protect your POP connection for several reasons, such as keeping your password from going across the line in the clear[5], or just to make sure no one's sniffing the email you're downloading.

Normally, your mail client will establish a TCP connection to the mail server on port 110, supply your username and password, and download your email. You can try this yourself using telnet or nc on the command line:

We can wrap this TCP connection inside an SSH session using SSH Port Forwarding. If you have SSH access to the machine that offers your service (POP, port 110 in this case) then SSH to it. If you don't, you can SSH to a server on the same network if the network is trusted. (See the security implications of port forwarding later in this article.)

In this case, let's assume we don't have SSH access to the mail server, but we can log into a shell server on the same network, and create a tunnel for our cleartext POP connection:

Before we connected to the shellserver with SSH, nothing was listening on port 9999 on our desktop - once we'd logged in to the mail server with our tunnel, this port was bound by our SSH process, and the TCP connection to local port 9999 was magically tunneled through SSH to the other side.

The SSH client binds the local port you specified, port 9999, on the loopback interface, 127.0.0.1.

You can do anything in your remote machine that you want -- tar up some files, write to some users, delete /etc/shadow... This interactive login is completely usable, or you can just let it hang around doing nothing.

When a process connects to 127.0.0.1 on port 9999 on the client machine, the /usr/bin/ssh client program accepts the conection.

The SSH client informs the server, over the encrypted channel, to create a connection to the destination, in this case mailserver port 110.

The client takes any bits sent to this port (9999), sends them to the server inside the encrypted SSH session, who decrypts them and then sends them in the clear to the destination, port 110 of the mailserver.

The server takes any bits received from the destination, mailserver's port 110, and sends them inside the SSH session back to the client, who decrypts and sends them in the clear to the process that connected to the client's bound port, port 9999.

When the connection is closed by either endpoint, it is torn down inside the SSH session as well.

As you can see, there's a brief mention of port 9999 being bound and available for tunneling. We haven't made a connection to this port yet, so no tunnel is active yet. You can use the ~# escape sequence to see the connections in use. This sequence only works after a carriage return, so hit enter a few times before trying it:

You can see that now we have both the SSH session we're using, plus a tunnel, the second entry. It tells you all you need to know about the connection -- it came from our local machine (127.0.0.1) source port 42789, which we could look up with netstat or lsof output if we were curious about it.

RemoteForward Example

SSH Forwards actually come in two flavours. The one I've shown above is a local forward, where the ssh client machine is listening for new connections to be tunneled. A Remote Forward is just the opposite - a tunnel initiated on the server side that goes back through the client machine.

The classic example of using a Remote Forward goes something like this. You're at work, and the VPN access is going to be down for maintenance for the weekend. However you really have some important work to do, but you'd rather work from the comfort of your desk at home, rather than being stuck at work all weekend. There's no way for you to SSH to your work desktop because it's behind the firewall.

Before you leave for the evening, you SSH from your work desktop back to your home network. Your ~/.ssh/config file has the following snippet:

We've set up a tunnel using the RemoteForward option in the SSH configuration file. (We could have set it up on the command line using the -R option if we'd prefered.) Just to make sure our firewall doesn't kill the connection for inactivity, we run a ping for grins. Then we head on home.

Later that evening, we can sit down on our home machine and see that we're logged in:

Now comes the payoff - our tunnel is listening on our home machine on port 2222, and will be tunneled back through the corporate firewall to our work machine's port 22. So to SSH to work from home, since we have our tunnel ready, we simply point /usr/bin/ssh to port 2222:

Port Forwarding Cheat Sheet

Remembering how to specify the kind of SSH Forward you want is sometimes tricky. Hopefully, the following table will make it a bit easier.

LocalForwards

Command Line Option

-L local_listen_port:destination_host:destination_port

Configuration file entry

LocalForward local_listen_port:destination_host:destination_port

local_listen_port is on

SSH client loopback interface

destination_host is contacted from

SSH server host

RemoteForwards

Command Line Option

-R remote_listen_port:destination_host:destination_port

Configuration file entry

RemoteForward remote_listen_port:destination_host:destination_port

remote_listen_port is on

SSH server loopback interface

destination_host is contacted from

SSH client host

Forwards can be confusing - we typically think of connections as being made up of four things - the local IP and port, and the remote IP and port. In the forward definition you create, you only have three things because the first port is always either the SSH client or server machine, and thus isn't specified.

Port Forward Security

Port forwards bind a port on either the ssh client (Local Forwards) or ssh server (Remote Forwards). With a default installation, the port will only be bound on the localhost interface, 127.0.0.1. This means that the tunnel is only available to someone on the machine where that port is listening.

In general, you don't want to allow other machines to contact your SSH tunnel so this is the correct setting. If you want to allow these ports to be available to any machine, then use one of the following:

Command line option

Configuration file option

LocalForwards

-g

GatewayPorts yes
(in ~/.ssh/config or /etc/ssh/ssh_config)

RemoteForwards

(none available)

GatewayPorts yes
(in /etc/sshd_config)

The other important thing you must remember is that the data connection is only encrypted inside the SSH connection. If your destination_host you specify is not localhost[6] then the portion of the connection that extends out of the tunnel is not encrypted. For example if you used the the following:

desktop$ ssh -L 8080:www.example.com:80 somemachine

then any connection to localhost:8080 will be encrypted from your desktop through to somemachine, but it will be in cleartext from somemachine to www.example.com. If that fits your security model, no problem. But keep it in mind.

We'll see how you can put further limits on port forwards in a future article, such as rejecting or limiting them based on the SSH Pubkeys/Identity that is used for authentication.

[4] Some POP server software offers SSL-encrypted POP, by negotiating SSL using STARTTLS on port 110, or wrapped entirely in SSL on port 995. For this example, however, let's assume you have a non-SSL aware POP server.

[5] Some POP servers support alternate authentication methods, such as S/Key or challenge response, which can keep your password from going across the network.

[6] Unsniffable options would be localhost, 127.0.0.1 (or any 127/8 address on most unix-like systems) or any local IP address - these should all go through the local machine's TCP/IP stack without hitting the network card at all, and thus would be as secure as the network stack itself.