SSH local/dynamic/reverse port forwarding for dummies

Or “how to connect to a server that is protected by a firewall that blocks all incoming (inbound) connections”.

To implement this, I’m using a Windows laptop running two virtualized CentOS instances using VMWare player:

  • The first CentOS instance (“centos1.entropysoft.net”) is going to connect to a web server running on…
  • The second CentOS instance (“centos2.entropysoft.net”).
  • On centos2:
  • I will be logged on centos1 using user “laurent1” and on centos2 using user “laurent2”.

Registering the CentOS server names in the DHCP/DNS

Not really related to this post, but I always forget how to do it: I had to modify the /etc/sysconfig/network-scripts/ifcfg-eth0 file so that the DHCP correctly registers the server name in the DNS:


[laurent@centos1 network-scripts]$ cat ifcfg-eth0
HWADDR=00:0C:29:C6:C7:30
TYPE=Ethernet
BOOTPROTO=dhcp
DEFROUTE=yes
PEERDNS=yes
PEERROUTES=yes
IPV4_FAILURE_FATAL=yes
IPV6INIT=no
NAME=eth0
UUID=7a3a6545-5f7e-4668-afd6-f511714aba67
ONBOOT=yes
DHCP_HOSTNAME=centos1

For more information, check this post.

Configuring the firewall on centos2

For these series of tests, the firewall on centos2 will need to block/unblock inbound/outbound connections on port 22 (SSH), 80 (HTTP) & 443 (HTTPS) so I will use this simple iptables script:

#!/bin/bash
# Reset
iptables -F

# Changing OUTBOUND policies to DROP (instead of ACCEPT)
iptables -P INPUT DROP
iptables -P OUTPUT DROP

# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Allow icmp
iptables -A INPUT -p icmp -j ACCEPT
iptables -A OUTPUT -p icmp -j ACCEPT

# allow INBOUND SSH
iptables -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT

# allow INBOUND HTTP
iptables -A INPUT -i eth0 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT

# allow INBOUND HTTPS
iptables -A INPUT -i eth0 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEPT

# allow OUTBOUND SSH
iptables -A OUTPUT -o eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT

# allow OUTBOUND HTTP
iptables -A OUTPUT -o eth0 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT

# allow OUTBOUND HTTPS
iptables -A OUTPUT -o eth0 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEPT

# allow DNS (otherwise www.XXX.com will never be resolved)
iptables -A OUTPUT -p udp -o eth0 --dport 53 -j ACCEPT
iptables -A INPUT -p udp -i eth0 --sport 53 -j ACCEPT

By commenting/uncommenting the needed lines and running the script, you can easily block/unblock the needed protocols (well, not exactly, since we would need an application layer firewall for this, but that’s another story). Note that these firewall rules will be lost after rebooting the centos2 server, which is what we want when testing !

Configuring the web server on centos2

As I said, there is a web server running at http://centos2.entropysoft.net:80. I’m simply using Apache that is listening on port 80 and serving this simple HTML page:

[laurent2@centos2 html]$ cat /var/www/html/index.html
<html>
<body>
Hello from centos2
</body>
</html>

Alternatively, you can use the python SimpleHTTPServer module:


[laurent2@centos2 Documents]$ pwd
/home/laurent2/Documents
[laurent2@centos2 Documents]$ cat index.html
<html>
<body>
Hello from centos2
</body>
</html>
[laurent2@centos2 Documents]$ sudo python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...

Exercice #1: no port forwarding

In this exercise, the centos2 firewall allows incoming HTTP connections.

To test this, simply run the iptables script above and then… well that’s it: open your web browser on centos1 and connect to http://centos2.entropysoft.net:80. Or using curl:

[laurent1@centos1 ~]$ curl http://centos2.entropysoft.net:80
<html>
<body>
Hello from centos2
</body>
</html>

Exercice #2: SSH local port forwarding

In this exercise, the centos2 firewall blocks inbound HTTP, but allows inbound SSH.

To test this, simply disable the “allow INBOUND HTTP” rule and run the iptables script: this should prevent the curl command used above from working (since the firewall now prevents centos1 to connect to centos2 on port 80).

To connect to our webserver, we are going to use the “local port forwarding” capacities of the SSH tool, which will allow us to:

  • use the SSH channel between the two servers to “tunnel” the HTTP traffic
  • encrypt the traffic

First, on centos2, let’s make sure that the sshd daemon is running:

[laurent2@centos2 bin]$ sudo service sshd status
openssh-daemon (pid 2095) is running...

The ssh daemon listens on port centos2:22 by default and we can connect to it from centos1:

[laurent1@centos1 ~]$ sudo ssh -L 9999:centos2.entropysoft.net:80 laurent2@centos2.entropysoft.net
laurent2@centos2.entropysoft.net's password:
Last login: Mon Oct 22 16:32:51 2012 from centos1.entropysoft.net
[laurent2@centos2 ~]$

We are now connected to centos2 (from centos1) and we can execute remote commands using the SSH shell: nothing fancy here.

The nice part is the “-L” command line that basically says “hey you SSH: can you please listen to all requests that arrive at centos1:9999 and forward them to centos2:80 ?”.

You can use the netstat command to check that you indeed have port centos1:9999 listening for incoming connections:

[laurent1@centos1 ~]$ sudo netstat -apn | grep 9999
tcp 0 0 127.0.0.1:9999 0.0.0.0:* LISTEN 6266/ssh
tcp 0 0 ::1:9999 :::* LISTEN 6266/ssh

And since your webserver is listening on port centos2:80, you can now access it from centos1 by connecting to http://localhost:9999 (yes, it works, even if there is not webserver on centos1: SSH silently redirects you to centos2 behind the hood !)

[laurent1@centos1 ~]$ curl http://localhost:9999
<html>
<body>
Hello from centos2
</body>
</html>

Of course, the “local port” (9999) and the “remote port” (80) do not need to be different. For example, you could use “-L 80:centos2.entropysoft.net:80” and it would work exactly the same.

Note that if you try “curl http://centos1.entropysoft.net:9999&#8221; instead of “curl http://localhost:9999&#8221;, it will not work since the netstat command above shows that centos1 only allows connections coming from locahost. To use “centos1.entropysoft.net:9999” you need to launch SSH with “-L centos1.entropysoft.net:9999:centos2.entropysoft.net:80″.

Exercice #3: SSH dynamic port forwarding

In this exercise, the centos2 firewall blocks inbound HTTP, but allows inbound SSH (exactly like in the previous exercise).

The “issue” with the previous exercise is that you have to explicitely say that you want to redirect port centos1:9999 to port centos2:80 (that’s the “-L” option remember ?). But what if you want to say “each local connection to port centos1:XX needs to be redirected to the same port on centos2” ?

Well, you can, thanks to the SSH -D option.

To use it, first close the SSH connection from the previous exercise and open a new one (you are still on centos1) like this:

[laurent1@centos1 ~]$ ssh -D 9999 laurent2@centos2.entropysoft.net
laurent2@centos2.entropysoft.net's password:
Last login: Mon Oct 22 16:57:25 2012 from centos1.entropysoft.net
[laurent2@centos2 ~]$

You know have a SOCKS proxy running on centos1 (and listening on port 9999). From there, let’s use curl again to connect to our webserver:

[laurent1@centos1 ~]$ curl --socks5 localhost:9999 http://localhost:80

<html>
<body>
Hello from centos2
</body>
</html>

If you do not want to use curl and prefer to use Firefox, go to the “Edit > Preferences > Network > Settings” window to set the SOCKS proxy:

So what’s the difference with the previous example you may ask ? Well, with the SOCKS proxy approach, your centos2 webserver can be running on any port (80, 8080, 8081 etc…) it doesn’t matter: you can access it from centos1 without having to know the port in advance when creating the SSH connection !

Exercice #4: SSH reverse local port forwarding

In this exercise, the centos2 firewall blocks ALL inbound connections, but allows outbound SSH connections.

OK, so now the firewall doesn’t allow any connection to be established from centos1 to centos2  (run the iptables script after commenting out all INBOUND rules), so how can we access the webserver ? Well, we need to use the “hollywood principle”: “don’t call me, I’ll call you”.

Said differently, centos2 is going to open a connection to centos1 and centos1 will use this open channel to communicate with centos2 (sneaky huh ?).

So, since centos2 is going to connect to centos1, we need to first make sure that the ssh daemon is running on centos1.

[laurent1@centos1 ~]$ sudo service sshd status
openssh-daemon (pid 2077) is running...

Then, switch to centos2 and enter this command (make sure that the SSH session from the previous exercice is stopped on centos1)

[laurent2@centos2 bin]$ sudo ssh -R 9999:localhost:80 laurent1@centos1.entropysoft.net
laurent1@centos1.entropysoft.net's password:
Last login: Mon Oct 22 17:43:02 2012 from centos2.entropysoft.net
[laurent1@centos1 ~]$

This time, we don’t use the “-L” option, but the “-R” option. This means “listen for connections on centos1:9999 and forward them to localhost:80 (localhost being centos2)”. Note that this does exactly the same thing that in the second exercise, except that here we are entering this command from centos2.

Now if you go back to centos1 and use the netstat command, you’ll see that the ssh(ssh daemon) process is listening for connections on port centos1:9999.

[laurent1@centos1 bin]$ sudo netstat -apn | grep 9999
tcp 0 0 127.0.0.1:9999 0.0.0.0:* LISTEN 6890/sshd
tcp 0 0 ::1:9999 :::* LISTEN 6890/sshd

You can now access the webserver from centos1:

[laurent1@centos1 ~]$ curl http://localhost:9999

<html>
<body>
Hello from centos2
</body>
</html>

The curl command line is exactly the same as the one we used in the second exercise, the only difference is that the SSH tunnel was launched from centos2, allowing the firewall to be traversed.

Exercice #5: SSH reverse dynamic port forwarding

In this exercise, the centos2 firewall blocks ALL inbound connections, but allows outbound SSH connections  (exactly like in the previous exercise).

We are going to do what we did in the “dynamic port forwarding” exercice, but this time by initiating the SSH tunnel from centos2. Also – and that’s the tricky part – the SOCKS proxy will be created on centos2, which means that we need to run two SSH commands from centos2.

First, the one to create the SOCKS proxy (yes: we are on centos2 and we use the SSH command to connect to centos2)

[laurent2@centos2 ~]$ sudo ssh -D centos2.entropysoft.net:9999 laurent2@centos2.entropysoft.net
laurent2@centos2.entropysoft.net's password:
Last login: Wed Oct 24 13:37:54 2012 from centos2.entropysoft.net
[laurent2@centos2 ~]$

Then, the second one to create the SSH tunnel (we are still on centos2):

[laurent2@centos2 ~]$ sudo ssh -R 9998:centos2.entropysoft.net:9999 laurent1@centos1.entropysoft.net
laurent1@centos1.entropysoft.net's password:
Last login: Wed Oct 24 13:38:53 2012 from centos2.entropysoft.net
[laurent1@centos1 ~]$

So in the end, this is what we have:

  • on centos1, we have the ssh daemon process listening on port 9998 (thanks to the “-R” command launched from centos2).
  • when connecting to this port on centos1, we will be redirected to port centos2:9999.
  • and we will be connected to the SOCKS proxy, which is listening for incoming connections on the centos2:9999.

So now we can go to centos1 and execute the exact same curl command that in the “dynamic port forwarding exercise”:

[laurent1@centos1 ~]$ curl --socks5 localhost:9998 http://localhost:80

<html>
<body>
Hello from centos2
</body>
</html>

Laurent KUBASKI

Advertisements

About lkubaski
www.kubaski.com

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: