Whitelisting SSH Access with OpenWRT

Display mode

Back to Articles

Sometimes, I take the liberty of looking through the log files of my server. Invariably, there's something like the following at the bottom:

Sample SSH log file

May  8 01:46:43 adhocbox sshd[28514]: Invalid user tanta from 61.100.x.x
May  8 01:46:46 adhocbox sshd[28516]: Invalid user cornel from 61.100.x.x
May  8 01:46:49 adhocbox sshd[28518]: Invalid user ronaldo from 61.100.x.x
May  8 01:46:51 adhocbox sshd[28520]: Invalid user wave from 61.100.x.x
May  8 01:46:54 adhocbox sshd[28522]: Invalid user vanilla from 61.100.x.x
May  8 01:46:57 adhocbox sshd[28524]: Invalid user ice from 61.100.x.x
May  8 01:47:02 adhocbox sshd[28526]: Invalid user mason from 61.100.x.x

This is repeated for a few hundred lines, and is followed a few hours later by another batch of attacks from another IP. It gets tiresome for one's bandwidth to be taken up by these attempts at logins, especially when the server only has one valid user, as is the case for my setup.

I decided to put an end to this, by implementing a whitelist: allowing specific IPs through at the firewall, and blocking all others. Fortunately, I run an OpenWRT installation on my Internet router, which provides Linux's iptables infrastructure for the manipulation of firewall rules. In this article, I'll detail how I set up my whitelist system, and how you can do the same.

What you'll need

My network consists of a Linksys WRT54G wireless router hosting the firewall, and a webserver running a distribution of Linux. For the purposes of this setup, the particulars of the webserver aren't an issue, but you will need:

OpenWRT:
My Linksys router has been reflashed with OpenWRT Whiterussian, which provides the iptables firewall, along with simplification scripts for the firewall rules. Any Linux box can act as the firewall, as long as you can pull the appropriate formatting together for the rules.
PHP with PECL-SSH2:
OpenWRT provides SSH access to the router, which allows for direct editing of the firewall configuration file. We'll be using this to our advantage, by programmatically adding IPs to the whitelist using PHP.
An external computer:
The easiest way to test the whitelist setup is by using a computer that's outside the LAN; this will allow you to check that packets are being appropriately blocked at the router, which will not necessarily be the case if you're going between computers inside the LAN.

The OpenWRT Firewall

OpenWRT provides a simple wrapper over the Linux iptables interface, using awk to rewrite the contents of a configuration file into filtering and NAT rules, which are then applied by an init script. There's also a wrapper on top of that, which constitutes the Web interface to the firewall; it is this interface that most people associate with the OpenWRT firewall.

The major issues with the Web interface are that it's relatively clunky, especially when it comes to changing the order of firewall rules; new rules are added to the bottom of the list, and moving them to the top involves an arduous series of clicks and page loads. For most purposes, direct editing of the configuration file makes more sense.

A simple configuration may contain among its rules the following:

OpenWRT's /etc/config/firewall: A sample

accept:dport=113 src=192.168.0.0/24
forward:proto=tcp dport=22:192.168.0.1:22
forward:proto=tcp dport=80:192.168.0.1:80
drop

This sample script will allow the firewall to accept Ident requests from inside the LAN, forward SSH and HTTP to a server at 192.168.0.1, and drop everything else. The parameters to each rule are parsed out by the init script, and built into iptables rules.

Just as with iptables, these rules are processed in order, and the first rule to match the incoming packet is applied. By using this principle, it's simple to put together a ruleset which will act as a whitelist for SSH:

Whitelisting SSH: firewall ruleset

forward:proto=tcp src=[IP #1] dport=22:192.168.0.1:22
forward:proto=tcp src=[IP #2] dport=22:192.168.0.1:22
drop:proto=tcp dport=22

In this example, any SSH packets coming from specific external IPs will be forwarded to the SSH server, and any other SSH packets will be dropped at the firewall. This is the behaviour which allows a whitelist: the next problem is how to add IPs to the list.

Adding IPs to the Whitelist

There are two ways to add addresses to this firewall ruleset. The first is to SSH into the OpenWRT router, edit the configuration file to add the appropriate rule, and then restarting the firewall service:

Manually updating the whitelist

ssh -l root 192.168.0.254
vim /etc/config/firewall
/etc/init.d/S45firewall restart

The problems with this method are two-fold:

Ease of use:
This manual method of updating the list doesn't constitute the most user-friendly interface to addition of IPs, and it can get tiresome to add IPs months or years after the whitelist is initially put into place.
Access:
Almost exclusively, access to the router's SSH port is only available from inside the LAN. From an external viewpoint, this availability will only exist by connecting from the accessible SSH server residing on the LAN. This in turn is governed by the whitelist, held on the router. The eponymous Catch-22 situation is an apt description of this problem.

Instead of using a manual process to update the list, it's possible to provide an externally-accessible interface to add IPs. In my case, I have a Web server (which happens to be my SSH server), so I can use a Web script to provide this interface; for the purposes of this article, PHP has been used as the language doing the work.

PHP doesn't have an interface to SSH version 2 by default: this is provided by a PECL extension named ssh2. Once this has been put in place, a variety of methods are exposed to allow for SSH connections to be made. These can be used to perform work on the OpenWRT router:

Use PECL_ssh2 to connect to the router

$ssh = ssh2_connect('192.168.0.254');
if(ssh2_auth_password($ssh, 'root', '[router root passwd]'))
{
	$stream = ssh2_shell($ssh);
	fwrite($stream, 'touch /tmp/newfile');
}

As an aside, if you don't like having the router's root password lying around in a PHP file, the PECL ssh2 extension also provides a public key authentication mechanism, and the SSH server on an OpenWRT installation allows addition of public keys in the same manner as OpenSSH.

Using PHP to automatically add IPs

Opening an interactive shell with ssh2_shell allows more than one command to be executed, which means we can do the file manipulation required to add an address to the list. We can combine everything, to produce the following script.

ssh.php: Add an IP to the router's whitelist

<?php
if(isset($_POST['add'])):
	$ssh = ssh2_connect('192.168.0.254');
	if(ssh2_auth_password($ssh, 'root', '[router root passwd]'))
	{
		$fp = ssh2_shell($ssh);
		fwrite($fp, 'echo "forward:proto=tcp src='.$_POST['ip'].' dport=22:192.168.0.1:22" > /tmp/1'."\n");
		fwrite($fp, "cp /etc/config/firewall /tmp/2\n");
		fwrite($fp, "cat /tmp/1 /tmp/2 > /etc/config/firewall\n");
		fwrite($fp, "/bin/sh /etc/init.d/S45firewall\n");

		// Provide enough time for the firewall to restart
		sleep(10);
	}
	echo "Done.";
else:
?>
<form method="post">
 <input type="text" name="ip">
 <input type="submit" name="add" value="Add">
</form>
<?php
endif;
?>

All that's required now is to navigate to this script, put an IP into the box, and wait 10 seconds. When this process has completed, the IP has automatically been added to the top of the firewall script, and the firewall restarted.

That should be everything you need to set up your own whitelist access list for SSH. No more brute-force attacks against your server!

Copyright Imran Nazar <tf@oopsilon.com>, 2008