This is the fourth post in our SSH forwarding series. In the previous posts, we saw how we can create a local port fowarding session, a remote port forwarding session, and a dynamic one (SOCKS5 proxy). In this article, we'll list some of the best practices to make port forwarding work for you not against you. So, let's get started:

Local port forwarding

Which IP address should start the tunnel?

When you create an SSH tunnel, you should be very careful about the machine from which is going to accept connections to start the tunnel. The most common (and secure) scenario is to limit your tunnel to localhost on the tunnel start and also localhost on the other end. Let's see an example to know why.

Choosing localhost in local and reverse SSH tunnels

In the local port forwarding example, we used a command like the following:

ssh -L 8080:localhost:80 remotehost

As a refresher, this will create a listening port on 8080 on your local machine and forward any traffic coming through it to remotehost on port 80. The thing to notice here is that: since I'm using localhost, this is going to bind the port 8080 on my local machine to 127.0.0.1 AND attach it to port 80 also on 127.0.0.1 on the remote machine. This means that nobody can access this tunnel except by connecting locally from my machine port 80 on the remote machine should not necessarily be bound to the public interface. This way, we can restrict access to this port to be granted only thorugh an SSH tunnel. So, I can rewrite the above command, ignoring SSH shortcuts as follows:

ssh -L 127.0.0.1:8080:127.0.0.1:80 remotehost

What's the difference?

Nothing, really. SSH will automatically bind port 8080 to the loopback interface on your local machine. The second 127.0.0.1 here is the remote bind address. This effectively means: I want the tunnel to reach port 80 that is exposed on the loopback interface of the remote machine.

Example use case

This is usefull if this service is restricted to be listening only on the loopback interface.

Make your local machine listen on all interfaces

Although this is not a good security practice, but here is how you can make your localhost open the tunnel to anybody who has network access to your machine:

ssh -L 0.0.0.0:8080:127.0.0.1:80 remotehost

You can test this by firing up your browser and navigating to your public IP address instead of 127.0.0.1 like http://x.x.x.x:8080. You should immediately be redirected (through the tunnel) to port 80 on remotehost (although the URL won't change).

Using another machine as your gateway

So far, we've been using localhost as our gateway to connect to the remote host. However, you can use a third machine as a gateway as follows:

ssh -L 8080:remotehost:80 gateway

Notice here how the order of the hosts has changed. The remote host I want to eventually tunnel to moves in place of the remote bind address and the new server that I introduced to the command (gateway) moves in place of the remote host. The important thing to notice here is that since I don't have the option to sepcify a remote bind address, the remote machine must expose port 80 to the public interface. Otherwise, gateway will not be able to establish the connection and the tunnel would be broken.

Example use case

This is useful when you do not have access to the remote host (because of a firewall for example) but you do have SSH access to a machine (gateway) that happens to have access to the remote host. Of course you could have just established an SSH session with the gateway first and opened the tunnel from there like:

# From the gateway server
ssh -L 0.0.0.0:8080:remotehost:80 remotehost

But this will make your gateway server, abd the tunnel beneath it, open to anyone. So, the better security practice is to initiate the tunnel from your local machine, passing through the gateway, to reach the remote host. Now, only people who are authorized to access your machine can use this tunnel.

Remote/Reverse port forwarding

The same rules that apply to local port forwaring apply to the reverse one but, well, in reverse! let's see how. Reverse port forwarding works by allowing access to a local port on your local machine that is restricted either by being made to listen on the loopback interface, or by a firewall. Let's use the previous example but this time the web server will be running on your local machine and you want to give specific people access to it using an SSH tunnel:

ssh -R 8080:localhost:8080 remotehost

Again, SSH protects you by binding the remote IP to the loopback interface of the remote machine. To have a working example, I'm going to use NodeJS as it is very easy to setup (you can use whatever application that will act as a web server for this lab: Apache/nginx/Python…etc.) So, I will create a file called server.js and add the following:

var http = require('http');
http.createServer(function (req, res) {
  res.write('This is my local machine'); 
  res.end(); //end the response
}).listen(8080,'127.0.0.1'); 

Notice that I am setting up the server to listen only on the loopback interface. Now, I will fireup the server by issuing node server.js. You can test the configuration by navigating to http://loaclhost:8080. You should see “This is my local machine”. Now, let's say I need to enable access to this port from outside but only to selected people: those who have access to the SSH tunnel. Using the above command, only people who has authorized access to remote host can have access to my machine by navigating to http://localhost from the remote host. But, what what if you want to allow people to access your machine from remote host by just navigating to http://remotehost? Although this is NOT a good security practice; as you'd be exposing an internal, local service on your local machine to the public Internet, this can be done by modifying the command to be as follows:

ssh -R 8080:0.0.0.0:8080 remotehost

But before issuing the command, and because of its potential security threats, the OpenSSH daemon configuration on the remote host must be altered to allow for this type of tunneling. Open /etc/ssh/sshd_config on the remtote machine and uncomment or add the following line

GatewayPorts yes

Restart the daemon by issuing systemctl restart sshd. Now you can establish the tunnel by executing the above command. Navigating to http://remoteohost will connect the user to port 8080 running on your loccal loopback interface!

Using your own machine as a gateway between two hosts that are behind different firewalls

You can also create a reverse tunnel making your local machine as a gateway. For exampl, server1 and server2 might be behind firewalls so they cannot connect to each other. But your machine has access to both of them. Using a reverse SSH tunnel you can use your own machine as a gateway to enable connection between them. Consider the following command:

ssh -R 8888:server2:8080 server1

In this example, server1 can establish a connection to server2 by using localhost:8888 for an address. Again, notice that SSH protects the channel by binding it to localhost by default. This means that server1 can access port 8080 on server2, but only if you are logged in to server1. If you want to make the tunnel public (I must repeat that this is a bad security practice), you can modify the tunnel command to be as follows:

ssh -R 0.0.0.0:8888:server2:8080 server1

This will bind port 8888 on all the interfaces of server1, including the public and the loopback interfaces. Notice that, using this scenario, port 8080 on server2 must be bound to the public interface. Otherwise, your local machine will not be able to reach it to establish the tunnel.