SSH Port Forwarding

All too frequently, after people setup a firewall to protect themselves, they discover it sometimes keeps them out as well. There are several solutions to consider when this comes up:

  1. Poke [temporary] holes in the firewall with firewall rules (if the firewall is under your control).
  2. Use a virtual private network to tunnel all client traffic through the firewall.
  3. Use ssh port forwarding to tunnel specific TCP connections though the firewall.
  4. Use ssh port forwarding to tunnel another ssh session (including all its port forwardings) through a firewall.

SSH port forwarding is probably the simplest and least well understood, so what follows will attempt to show what it can do and when it is useful.

For starters, ssh port forwarding is useful if (and only if) there exists another host to which you can ssh, which (by virtue of its different location on the network) has access to what you want.

SSH Port forwarding always forwards traffic bidirectionally but you must specify which end of the ssh tunnel accepts connections to forward. If the same host which initiates the ssh connection also accepts connections to forward, that's called "local port forwarding". Local port forwarding is probably the most convenient to use and easiest to understand so that's mostly what will be described here. (Just remember that for the most part, forwarding a local port on an outbound ssh connection is equivalent to forwarding a remote port on an inbound ssh connection.)

STATIC PORT FORWARDING

SSH version 1 (such as TeraTerm) only implements static port forwarding. Static port forwarding requires no application configuration changes to use but does require explicit references to the forwarded ports. (If you can configure your applications to use it, you may prefer Dynamic Port Forwarding (described later) because it is more transparent to use.)

Example 1: Statically forwarding a URL

If you establish the following ssh connection from host "YourPC" to host "homer.u" with port forwarding:

	ssh -L3210:staff.washington.edu:80 homer.u
and then access the URL below from "YourPC":
	http://localhost:3210/corey/info.cgi
you will find it behaves as if you'd accessed:
	http://staff.washington.edu:80/corey/info.cgi
directly from "homer.u" (note the value of REMOTE_HOST in that webpage to confirm).

In the example above, the data would travel encrypted/tunneled between "YourPC" and "homer.u" and then be sent unencrypted between "homer.u" and "staff.washington.edu".

You can forward as many ports to as many hosts as you want on one connection or you can login repeatedly forwarding new ports as needed on multiple ssh connections.

As a security precaution, modern versions of ssh forbid other computers on the internet from connecting to the forwarded port (3210 on "YourPC" in the example above) unless you explicitly allow it with the "ssh -g" flag. With the "-g" flag, ssh will allow any host on the internet to connect to and use the tunnel so use this with caution.

Probably the best thing about static ssh port forwarding is that it is so easy to do for occasional tunnels from dynamic IP addresses (which are hard to pre-configure into firewall rules). Also, no system, application or protocol configuration changes are needed to use them. On the other hand, it can be confusing to keep track of which ports are forwarded to which hosts so this probably will be impractical for more than a few forwarded ports.

DYNAMIC PORT FORWARDING

In addition to Static Port Forwarding (described above), the commercial version of SSH version 2 from ssh.com and recent versions of "OpenSSH" and "PuTTY" (both free) have an additional "dynamic" port forwarding option which is implemented as a "socks proxy server". This allows you to configure just one local port for all remote destinations. To use this "dynamic" port forwarding, you need to reconfigure applications (such as your web browser) to send all their traffic using the "socks protocol" to this one local port. The application (eg. browser) uses the "socks" protocol to specify where the traffic should be sent when it leaves the other end of the ssh tunnel. (Note OpenSSH is free and available for Unix/Linux, Mac OS/X, and Windows (under cygwin (also free)).

Example 2: Using dynamic port forwarding with a web browser

If you establish the following ssh connection from host "YourPC" to "homer.u" with dynamic port forwarding:
	ssh2 -L socks/3210 homer.u	# if using commercial ssh2
	ssh -2 -D 3210 homer.u		# if using a recent OpenSSH
and then configure your browser to use a "socks4 proxy" at
	IP address: localhost (127.0.0.1) and port: 3210
and then access any unmodified URL (such as below) from "YourPC":
	http://staff.washington.edu/corey/info.cgi
you will find it behaves as if you'd accessed it directly from homer.u (note the value of "REMOTE_HOST" in that webpage to confirm).

As with static port forwarding, in the example above, the data would travel encrypted/tunneled between "YourPC" and "homer.u" and then be sent unencrypted between "homer.u" and staff.washington.edu.

As a security precaution, modern versions of ssh forbid other computers on the internet from connecting to the socks proxy port (3209 on "YourPC" in the example above) unless you explicitly allow it with the "ssh -g" flag. With the "-g" flag, ssh will allow any host on the internet to connect to and use the tunnel so use this with caution.

OTHER EXAMPLES:

Example 3: Helping an application use a non-standard port

If you have an application (such as an email client) which has no configuration option to send to an arbitrary port but can be configured to send to localhost, you can use ssh port forwarding to make it send on an arbitrary port. If you can ssh to "localhost", the first line will do, otherwise you can use something like the second:

	ssh -L25:smtp.washington.edu:587 localhost
	ssh -L25:smtp.washington.edu:587 homer.u
Here, any traffic the email client sends to localhost on port 25 will be forwarded to "smtp.washington.edu" on port 587.

Example 4: Remote port forwarding - exposing an internal webserver

Remote port forwarding is sometimes useful for granting temporary access to services on clients which are otherwise unreachable (perhaps the client has an unreachable rfc1918 address behind a NAT or firewall). If the client is not configured to accept inbound SSH connections, this can be done with remote port forwarding on an outbound connection. (For OpenSSH, note that you may need to set "GatewayPorts yes" in "sshd_config" to achieve on the server what "ssh -g" would do on the client (allow any host to connect to the forwarded port).

Consider a PC which is running a webserver but is protected by firewall rules to accept no inbound connections (except from itself). Furthermore imagine this host has the unrouted private address 192.168.1.1 and can access the internet only outbound through a NAT. If you believe it is not too risky, you can briefly make that server publicly available with ssh port forwarding (such as while you're on the phone with someone) if you think the risk of serving sensitive data to a random hacker while it is publicly exposed is acceptably low and you can ssh from the PC to a host with a public address. To do so, you can establish an ssh connection such as this:

	ssh -R4567:localhost:80 some-server
which will make it look like your PC's webserver is a server on "some-server" at port: 4567. Anyone who connects to:
	http://some-server:4567/some/path
will get the same webpage you get on your PC when you connect to:
	http://localhost:80/some/path

If you CAN ssh into your PC, you can do something similar (but probably more convenient) with an inbound ssh connection to gain similar access to the protected webserver on YourPC:
	ssh -L4567:localhost:80 YourPC
which you would use from wherever you're coming from as:
	http://localhost:4567:/some/path
Note that this is similar to Example 1 above but with the unencrypted part of the forwarding just going to localhost.

Example 5: Rerouting traffic for debugging

Imagine you're trying to debug a service on a remote server and you'd like to be able to see all the network traffic to/from the server with a network sniffer. You can temporarily change the server to offer its service on a different/temporary port; use ssh to forward/tunnel the original service port to your PC, and from your PC, send the traffic back to the different/temporary port on the remote server. This allows you to sniff it as it travels between your PC and the different/temporary port on the remote server.

For example, if you changed SMTP (email) service on "some-server" to listen on port 5432 instead of port 25 and then established this ssh connection from your PC:

	ssh -R25:some-server:5432 some-server
Data sent to port 25 on "some-server" would be sent tunneled/encrypted to your PC and then sent unencrypted back to "some-server" at port 5432 (to the real application). You would then be able to conveniently "sniff" it on your PC.

Example 6: Tunnelling Windows Remote Desktop Protocol (RDP)

(This is similar to Example 1 above but with different ports and described in terms of the Windows TeraTerm application.)

If you are off campus and you want to use the Windows Remote Desktop to connect to a Windows PC (with "Campus_IP") which has access restricted to "campus" only:

  1. Run TeraTerm
  2. Select setup->ssh forwarding; and click "add"
  3. For "local port forwarding", fill in:
    local port 3456, Campus_IP, remote port 3389; click OK
  4. Use file->new connection; to login to homer.u
  5. Use your normal Windows Remote Desktop program to connect to: "127.0.0.1:3456"
    to reach "Campus_IP:3389" through the tunnel.

Macintosh and Linux/Unix users will find steps #1-4 above are equivalent to:

	ssh -L3456:Campus_IP:3389 homer.u
Windows users, if they prefer, can do something similar by creating a desktop shortcut to Teraterm with similar command-line arguments:
	C:\...\ttssh.exe /ssh-L3456:Campus_IP:3389 homer.u


Example 7: Tunnelling SSH (with all its port forwardings) through SSH

This may sound silly at first but it is really quite a useful trick. Imagine that "final_host" will not accept any connections from you directly however it will accept them from "intermediate_host". The goal is to ssh to "final_host" in as normal a way as possible. Assuming that the number 4022 is an unused local port, consider this:

	1) ssh -L4022:final_host:22 intermediate_host	#create the tunnel
	2) ssh -p4022 localhost somecmd	# connect to final_host
	3) ssh -p4022 -X localhost	# connect to final_host

Line #1 encrypts and forwards port 4022 traffic from localhost to "intermediate_host" where it then heads "in the clear" to "final_host", however in this case, port 4022 will carry ssh traffic, which is end-to-end encrypted, so it is really singly encrypted between "intermediate_host" and "final_host" and doubly_encrypted between localhost and "intermediate_host".

Line #2 lets you run "somecmd" on "final_host" in one easy step (such as in a script) without scripting a secondary login through "intermediate_host" (which is hard to do).

Line #3 lets you easily and securely tunnel X window connections "directly" from localhost to "final_host". Trying to tunnel X first to "intermediate_host" and then to "final_host" any other way is less satisfactory and/or requires additional software.

Note that you can create an artibrary number of simultaneous ssh connections to "final_host" via the one forwarded port (each will exit the tunnel on a unique port).

Note also that if you have different passwords on "intermediate_host" and "final_host", the tunneled ssh connection to "final_host" is encrypted end-to-end and your password on "final_host" will be secure even if "intermediate_host" is compromised and running a compromised sshd (as long as your public key for "final_host" on localhost is correct). Similarly note that if you use a form of passwordless keypair authentication between localhost and "final_host", that should also work transparently and securely through the tunnel via "intermediate_host".

If you need to connect from localhost to more than one "final_host", besides forwarding several different ports, you also need to create and use unique local hostnames (entries in the local "hosts" file) for each "final_host" so ssh can associate the public key of each destination with a unique hostname in its "known_hosts" file. For the example below, these lines would be added to the local "hosts" file:

	127.0.0.1	local_final_host
	127.0.0.1	local_final_host2

And these would be the ssh commands:

	1) ssh -L4022:final_host:22 -L5022:final_host2:22 intermediate_host	# create two tunnels
	2) ssh -p4022 local_final_host somecmd	# to connect to final_host
	3) ssh -p4022 -X local_final_host	# to connect to final_host
	4) ssh -p5022 local_final_host2 somecmd	# to connect to final_host2
	5) ssh -p5022 -X local_final_host2	# to connect to final_host2

The numbers 4022 and 5022 are arbitrary, choose what makes sense to you.


Corey Satten
Email -- corey @ u.washington.edu
Web -- http://staff.washington.edu/corey/
Date -- Fri Feb 8 13:27:47 PST 2008