Stop SSH User Hacking / Cracking Attacks With DenyHosts Software

jbrown 17 February, 2008 17:50 Linux Permalink Trackbacks (0)

Debian Linux Stop SSH User Hacking / Cracking Attacks with DenyHosts Software

 

Q. I’ve noticed lots of failed login attempt for my Debian Linux VPS root server account. How do I stop automated bot based SSH attacks on my server?

A. You can use DenyHosts - a Python based script that analyzes the sshd server log messages to determine what hosts are attempting to hack into your system. It is an utility to help sys admins thwart ssh crackers. It also determines what user accounts are being targeted. It keeps track of the frequency of a (More)

Linux Text Editor: Vi

jbrown 17 February, 2008 17:31 Linux Permalink Trackbacks (0)
The vi editor comes with every version of Linux or Unix. It's a terribly unfriendly beast of an editor, but you should know about it because someday you're likely to find yourself on a system where you have no other choice but to use it. A friend of mine calls vi the Heart of Evil, but that might be just a bit harsh--you decide
 
Using vi is similar to using other editors in that you can see your file on the screen (this is not the case with a line editor, for example), move from point to point in the file, and make changes. But that's where the similarities end. Cryptic commands, a frustrating user interface, and the absence of prompts can all drive you up a wall. Still, if you focus on a few basics, you'll get the job done.

Command and Input Mode

The hardest thing to understand about vi is the concept of modes. When using vi, you're always in either Command or Input mode. Unfortunately there's no clue as to which mode is currently active. In Command mode, you can move the cursor, search for characters, and delete existing text. But to enter or edit new text, you have to switch to Input mode.

When you start vi, you're in Command mode. To enter Input mode, type the letter a (lowercase only) to signal that you want to add text after the cursor position. Press esc to switch back to Command mode at any time.

Here's how to create a file from scratch using vi. To start, create a new file named cow.joke by typing

vi cow.joke

You'll see a screen that looks like this:

|

~

~

~

~

~

~

~

~

~

"cow.joke" [New file]

Adding New Text to Your File

Your cursor (the vertical bar at the top of the figure below is in the upper-left corner of the screen, and the message at the bottom tells you that a new file called cow.joke was just created. The tilde characters in the first column are just placeholders for empty lines.

Now press the letter a to enter Input mode and type the lines shown in here. Press enter at the end of each line to go on to the next.

Jane: Knock, knock...

Bill: Who's there?

Jane: The Interrupting Cow.

Bill: The Interrupting Cow wh...

Jane: MOOOOOO!

|

~

~

~

~

"cow.joke" [New file]

Saving Your Work

So far, so good--let's save this little masterpiece. You're still in Input mode, so press esc to enter Command mode; then type ZZ (to put your file to sleep). You won't see any Z's on the screen, but after you've entered the second Z, your file will disappear, your Linux command prompt will return, and you'll see this message, indicating that your file was successfully saved:

"cow.joke" 6 lines, 113 characters.

Congratulations--you've just survived your first encounter with vi. You know that the a command switches to Input mode, esc gets you back to Command mode, and ZZ saves the file, but you'll have to expand this limited repertoire to get any real work done.

Common vi Commands

Have a look at this list of common vi commands (there are many more, but these will at least allow you to get some basic work done). Then we'll do one more exercise before moving on.

Note: As with all of Linux, vi commands are case sensitive.

Positioning the Cursor

® Move cursor one space right.
¬ Move cursor one space left.
­ Move cursor up one line.
¯ Move cursor down one line.
ctrl-F Move forward one screen.
ctrl-B Move backward one screen.
$ Move cursor to end of line.
^ Move cursor to beginning of line.
:1 Move to first line of file
:$ Move to last line of file
/ Search for a character string.
? Reverse search for a character string.
x Delete the character at the cursor position.
dd Delete the current line.
p Paste data that was cut with x or dd commands.
u Undo.

Entering Input Mode

a Add text after the cursor.
i Insert text before the cursor.
R Replace text starting at the cursor.
o Insert a new line after the current one.

Entering Command Mode

esc Switch from Input mode to Command mode.

Exiting or Saving Your File

:w Write file to disk, without exiting editor.
ZZ Save the file and exit.
:q! Quit without saving.

Trying Out Some vi Commands

Here's another example to try out some of the vi commands. Enter the following command to fire up vi again, and you should see the file as we left it in the last example:

vi cow.joke

Changing Text

Let's change Bill's name to Biff on the second line. To do so, use the arrow keys to position your cursor on the third character of line 2 (the letter "l" in Bill); then press x twice (to delete the two l's). Now press i (to enter Input mode) and then type ff to complete the change from Bill to Biff.

Tip: Be careful about pressing the arrow keys while you're in Input mode. In some versions of vi you can position the cursor only in Command mode. Yuck.

You could also have used the R command to do this job of replacing text, so use it to change the other Bill now. Press esc to enter Command mode; then type /Bill to search for the word Bill. The cursor should move to line 4, right to where Bill is located.

Now position your cursor on the third character (the letter "l"), press R to replace the characters, and type ff. Both Bills should now be Biffs.

Adding and Deleting Lines

Here's how to add or delete a line. Press esc to enter Command mode; then press the o key to add a new line. You're in Input mode again, so you can type whatever you like on this new line. But that would ruin the joke, so delete this new line by pressing esc and then entering the dd command. The line you just added should go away.

Quitting without Saving Your Changes

Hmmm. . . . "Biff" just doesn't have that wholesome ring to it, so let's forget about all the changes we've made in this editing session and exit vi without saving the file. Make sure you're in Command mode, enter the :q! command, and then press enter. Your Linux prompt should return, and the cow.joke file will be just as it was before.

Parting Words about vi

Using vi can be frustrating, but it really isn't rocket science once you get used to the concept of the two modes and get the hang of when it's okay to move your cursor or enter text. If you're ever unsure about which mode you're in, simply press esc once or twice, and you can be sure you're in Command mode.

There are some powerful (but arcane) commands that diehard vi users use to get things done quickly in this relic-of-the-sixties text editor. The man vi command will tell you a lot more about vi if you decide you want to become proficient.

The Usenet newsgroup comp.editors is a good place to discuss vi or ask questions. You can find the vi FAQ (Frequently Asked Questions) file on the Web at http://www.faqs.org/faqs/editor-faq/vi/.

For more information on the vi command, see the vi manual.


SSH: Best Practices

jbrown 17 February, 2008 17:04 Linux Permalink Trackbacks (0)

SSH: Best Practices

Introduction

Are you using SSH in the best way possible? Have you configured it to be as limited and secure as possible? The goal of this document is to kick in the new year with some best practices for SSH: why you should use them, how to set them up, and how to verify that they are in place.

All of the examples below assume that you are using EnGarde Secure Linux but any modern Linux distribution will do just fine since, as far as I know, everybody ships OpenSSH.

SSHv2 vs. SSHv1

There are numerous benefits to using the latest version of the SSH protocol, version 2, over it's older counterpart, version 1 and I'm not going into a lot of details on those benefits here - if you're interested, see the URL in the reference below or Google around. That being said if you don't have an explicit reason to use the older version 1, you should always be using version 2.

To use SSHv2 by default but permit SSHv1, locate the "Protocol" line in your sshd_config file and change it to:

Protocol 2,1

When doing 2,1 please note that the protocol selection is left up to the client. Most clients will default to v2 and "fall back" to v1, while legacy clients may continue to use v1. To force everybody to use SSHv2, change it to:

Protocol 2

When you make this change don't forget to generate the appropriate HostKey's as well! SSHv2 requires the following keys:

# HostKeys for protocol version 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key

While SSHv1 requires:

# HostKey for protocol version 1
HostKey /etc/ssh/ssh_host_key

Once your changes are made, restart the SSH daemon:

# /etc/init.d/sshd restart

[ SUCCESSFUL ] Secure Shell Daemon
[ SUCCESSFUL ] Secure Shell Daemon

From another machine, try SSH'ing in. You can use the -v option to see which protocol is being used, and the '-oProtocol=' option to force one or the other - for example, "ssh -v -oProtocol=2 " would force protocol version 2.

Binding to a Specific Address or Non-Standard Port

If you're running SSH on an internal, firewalled, workstation then you can probably skip this section, but if you're running SSH on a firewall or on a machine with two network interfaces, this section is for you.

Out of the box OpenSSH will bind to every available network address; while convenient and suitable for most installations, this is far from optimal. If your machine has two or more interfaces then the odds are that one is "trusted" and the other is "untrusted." If this is the case, and you don't need nor want SSH access coming in on the untrusted interface, then you should configure OpenSSH to listen on a specific interface.

To have OpenSSH only bind to your internal interface, 192.168.0.1 in the example below, locate the following line in your sshd_config file:

ListenAddress 0.0.0.0

and change the 0.0.0.0 to 192.168.0.1:

ListenAddress 192.168.0.1

To verify that this change took, restart OpenSSH and look at netstat:

# /etc/init.d/sshd restart

[ SUCCESSFUL ] Secure Shell Daemon
[ SUCCESSFUL ] Secure Shell Daemon

# netstat -anp | grep sshd

tcp 0 0 192.168.0.1:22 0.0.0.0:* LISTEN 7868/sshd

As you can see, the sshd daemon is now only listening on 192.168.0.1. SSH requests coming in any other interface will be ignored.

Similarly, you may want to change the port that the SSH daemon binds to. Sometimes there is a functional need for this (ie, your employer blocks outbound 22/tcp) but there is also security-through-obscurity value in this as well. While not providing any real security benefit against a determined attacker, moving the SSH daemon off of port 22 protects you against automated attacks which assume that the daemon is running on port 22.

To have OpenSSH bind to a port other than port 22, 31337 in the example below, locate the following line in your sshd_config file:

Port 22

and change the 22 to 31337:

Port 31337

To verify that this change took, restart OpenSSH and, again, look at netstat:

# netstat -anp | grep sshd

tcp 0 0 192.168.0.1:31337 0.0.0.0:* LISTEN 330/sshd

Finally, to SSH into a host whose SSH daemon is listening on a non-standard port, use the -p option:

ssh -p 31337 user@192.168.0.1

Using TCP Wrappers

TCP Wrappers are used to limit access to TCP services on your machine. If you haven't heard of TCP Wrappers you've probably heard of /etc/hosts.allow and /etc/hosts.deny: these are the two configuration files for TCP Wrappers. In the context of SSH, TCP Wrappers allow you to decide what specific addresses or networks have access to the SSH service.

To use TCP Wrappers with SSH you need to make sure that OpenSSH was built with the -with-tcp-wrappers. This is the case on any modern distribution.

As I indicated earlier, TCP Wrappers are configured by editing the /etc/hosts.deny and /etc/hosts.allow files. Typically you tell hosts.deny to deny everything, then add entries to hosts.allow to permit specific hosts access to specific services.

An example:

#
# hosts.deny This file describes the names of the hosts which are
# *not* allowed to use the local INET services, as decided
# by the '/usr/sbin/tcpd' server.
#
ALL: ALL
#
# hosts.allow This file describes the names of the hosts which are
# allowed to use the local INET services, as decided
# by the '/usr/sbin/tcpd' server.
#
sshd: 207.46.236. 198.133.219.25

In the example above, access to SSH is limited to the network 207.46.236.0/24 and the address 198.133.219.25. Requests to any other service from any other address are denied by the "ALL: ALL" in hosts.deny. If you try to SSH into a machine and TCP Wrappers denies your access, you'll see something like this:

ssh_exchange_identification: Connection closed by remote host

This simple configuration change significantly hardens your installation since, with it in place, packets from hostile clients are dropped very early in the TCP session -- and before they can do any real damage to a potentially vulnerable daemon.

Public Key Authentication

The last item I will cover is public key authentication. One of the best things you can do to tighten the security of your SSH installation is to disable password authentication and to use public key authentication instead. Password authentication is suboptimal for many reasons, but mostly because people choose bad passwords and attackers routinely try to brute-force passwords. If the systems administrator has chosen a bad password and he's permitting root logins... game over.

Public key authentication is no silver bullet - similarly, people generate passphrase-less keys or leave ssh-agents running when they shouldn't - but, in my opinion, it's a much better bet.

Just about every distribution ships with public key authentication enabled, but begin by making sure it is:

RSAAuthentication yes
PubkeyAuthentication yes

Both of these options default to "yes" and the "RSAAuthentication" option is for SSHv1 and the "PubkeyAuthentication" option is for SSHv2. If you plan on using this authentication method exclusively, while you're there, you may want to disable password authentication:

PasswordAuthentication no

Before you proceed, make sure you have a terminal open on your target machine. Once you restart the SSH daemon you will no longer be able to log in without a key... which we haven't generated yet!

Once you're sure, restart the SSH daemon:

# /etc/init.d/sshd restart

[ SUCCESSFUL ] Secure Shell Daemon
[ SUCCESSFUL ] Secure Shell Daemon

Now, from your desktop, try to SSH in to your target machine:

$ ssh rwm@brainy

Permission denied (publickey,keyboard-interactive).

We're locked out! This is a good thing. The next step, on your desktop, is to generate a key:

$ ssh-keygen -t dsa -C "Ryan's SSHv2 DSA Key (Jan 2008)"

Generating public/private dsa key pair.
Enter file in which to save the key (/home/rwm/.ssh/id_dsa):
Enter passphrase (empty for no passphrase): **********
Enter same passphrase again: **********
Your identification has been saved in /home/rwm/.ssh/id_dsa.
Your public key has been saved in /home/rwm/.ssh/id_dsa.pub.
The key fingerprint is:
98:4d:50:ba:ee:8b:79:be:b3:36:75:8a:c2:4a:44:4b Ryan's SSHv2 DSA Key (Jan 2008)

A few notes on this:

  • You can generate a DSA (-t dsa), RSA (-t rsa), or SSHv1 (-t rsa1) key. In the example above I'm using dsa.
  • I like to put the date I generated the key in the comment (-C) field, that way I can change it out every so often.
  • You're entering a passphrase, not a password. Use a long string with spaces and punctuation. The longer and more complicated the better!

The command you just ran generated two files - id_dsa, your private key and id_dsa.pub, your public key. It is critical that you keep your private key private, but you can distribute your public key to any machines you would like to access.

Now that you have generated your keys we need to get the public key into the ~/.ssh/authorized_keys file on the target machine. The best way to do this is to copy-and-paste it - begin by concatenating the public key file:

$ cat .ssh/id_dsa.pub

ssh-dss AAAAB3NzaC1kc3MAAACBAL7p6bsg5kK4ES9BWLPCNABl20iQQB3R0ymaPMHK...
... ds= Ryan's SSHv2 DSA Key (Jan 2008)

This is a very long string. Make sure you copy all of it and that you do NOT copy the newline character at the end. In other words, copy from the "ssh" to the "2008)", but not past that.

The next step is to append this key to the end of the ~/.ssh/authorized_keys file on your target machine. Remember that terminal I told you to keep open a few steps ago? Type the following command into it, pasting the key you've just copied into the area noted KEY:

echo "KEY" >> ~/.ssh/authorized_keys

For example:

echo "ssh-dss AAAA5kS9BWLPCN...s= Ryan's SSHv2 DSA Key (Jan 2008)" >> ~/.ssh/authorized_keys

Now, try to SSH in again. If you did this procedure correctly then instead of being denied access, you'll be prompted for your passphrase:

$ ssh rwm@brainy

Enter passphrase for key '/home/rwm/.ssh/id_dsa':
Last login: Thu Jan 10 14:37:14 2008 from papa.engardelinux.org
[rwm@brainy ~]$


Using Ssh Tunneling And A Remote Proxy

jbrown 17 February, 2008 16:33 Linux Permalink Trackbacks (0)
Using ssh tunneling and a Remote Proxy:

You can create an ssh tunnel to a remote system and use the remote system as a proxy.

Local system: Connect to remote system using ssh and dynamic port forwarding.

ssh -D port-number login-id@remote-node
Example:
ssh -D 9999 user1@node6.mega-corp.com
This establishes a connection to the remote node and configures traffic on local port 9999 to be forwarded to the remote system which acts as a proxy and fulfill the network request.

Mozilla/SeaMonkey Configuration:

  • Select "Edit" + "Preferences..."
  • On the left hand side of the window select and expand on the browser tree "Advanced" + "Proxies"
  • Select "Manual Proxy Configuration"
  • Select "SOCKS V4"
  • Set entries:
    • Socks Host: localhost
    • Port: 9999

Remote system: The service sshd must be running.

Browser requests will be forwarded to local port 9999 through ssh to the remote node which will fulfill the request. I have tried this for http web request as well as e-mail POP3 inbound mail and SMTP outbound mail and it works!


Sshdfilter V1.4.5 -- Ssh Brute Force Attack Blocker

jbrown 17 February, 2008 11:54 Linux Permalink Trackbacks (0)

-- sshdfilter V1.4.5 --
ssh brute force attack blocker


Introduction

sshdfilter blocks the frequent brute force attacks on ssh daemons, it does this by directly reading the sshd logging output and generating iptables rules, the process can be quick enough to block an attack before they get a chance to enter any password at all.

sshdfilter starts sshd itself, having started sshd with the -e and -D options. This means it can see events as they happen. sshdfilter then looks for lines of the form:
Did not receive identification string from x.x.x.x
Illegal user x from x.x.x.x
Failed password for illegal user x from x.x.x.x port x ssh2
Failed password for x from x.x.x.x port x ssh2

The former three instantly trigger sshdfilter into creating iptables rules which block all ssh access from that IP. The latter failure is given a few chances before it too is blocked. These are in fact example rules, the exact wording varies between Linux distributions, so sshdfilter exists as a base program and groups of patterns for each distribution.

All new rules are inserted into a custom chain, and to prevent the chain from becoming overloaded with old rules, rules over a week old are deleted.

Taking some statistics from my logs before sshdfilter was implemented, the longest attempt from a single IP was half an hour long, trying around 1 username/password pair per second. Over 26 days there were attacks from 42 unique IPs, none of them ever came back after the attempt. Picking the oldest 10 of these attacks, they had 347, 306, 115, 115, 127, 18, 554, 107, 9, 52 failed(!) password attempts. Of these 1750 attempts, 720 were for root - making a good case for blocking password authentication of the root account.

With sshdfilter installed, taking each attack on a case by case basis:
347 attempts becomes 0 attempts - first attemped guess was for a non-existant user, so was instantly blocked.
306 attempts becomes 0 attempts - same reason, non-existant user.
115 attempts becomes 1 attempt - first guess was for root and is allowed a default of 3 chances, the second guess was for a non-existant user and so was blocked anyway.
115 attempts becomes 1 attempt - same as previous.
127 attempts becomes 3 attempts - many initial guesses for root account, so sshdfilter blocks after the first 3 failed attempts.
18 attempts becomes 0 attempts - first attempted guess was for a non-existant user, so was blocked instantly.
554 attempts becomes 3 attempts - many initial guesses for root accont, so sshdfilter blocks after the first 3 failed attempts.
107 attempts becomes 1 attempt - first guess was for a valid user (nobody), second guess was for a non-existant user so was blocked.
9 attempts becomes 0 attempts - first guess was for a non-existant user so was blocked instantly.
52 attempts becomes 3 attempts - many initial guesses for root accont, so sshdfilter blocks after the first 3 failed attempts.

Summerising, of these 10 sample attacks, 1750 attempts, 720 were for root, 1013 were for illegal accounts, the rest were for existing accounts. Using only user name gusses alone, sshdfilter would have blocked 1729 of these. In fact, until the authors of the brute force attacks improve their code and send an ssh id string, sshdfilter would actually have blocked 1744 attempts, allowing only 6 guesses of the root account over an average of 6 days. Had password based root access been banned, even these six would have been futile.

Right now sshdfilter works with Debian 3.1, Redhat 7.3 to 9.0, Fedora Core 2-4, CentOS, Suse 10.0 RC 1, RedHat Enterprise Linux 4 and gentoo, Mandriva, and the many derivatives. Each distribution uses different logging messages, so sshdfilter needs a pattern list for the target system. Patterns are supplied for RedHat 7.3 and 9.0 systems, RedHat Fedora Core 3 systems, Suse 10.0 RC 1 and Debian 3.1 (sarge, stable) systems, the other distributions use patterns based on these 4. There is also support for Dropbear, a light weight ssh server.

Download

The really old stable version can be found here(V1.3.5).

The stable version can be found here(V1.4.5).

The web page for the testing version (V1.5.x) can be found here - if the previous versions aren't flexible enough for you, this version will be.

sshdfilter has been ported to Dropbear, and is available here(V1.4.3.2-dropbear). Dropbear is a light weight sshd server.

For a comparison, the changlog is below, and continues into 1.5.x here.

Installation (for <=sshdfilter 1.4.x)

This was written with common Linux distributions in mind, and so expects to find perl, the iptables command and support syslog style logging.

Add iptables chain with (/etc/sysconfig/iptables style):
:SSHD - [0:0]
or bash:
iptables -N SSHD

Add a jump to SSHD rule with something like:
-A INPUT -p tcp -m tcp --dport 22 -j SSHD
or bash:
iptables -I INPUT -p tcp -m tcp --dport 22 -j SSHD

This is only an example, I've no idea how you set up your iptables. Generally you'll have a line that ACCEPTS ssh(port 22), and the above should go on the line before. Notice I've used -I on the second example, which adds to the start of the chain instead of the end. The -I has more chance of working when added to a running system. The command:
iptables -L INPUT -vn
should show something like:
----
Chain INPUT (policy ACCEPT 22M packets, 1901M bytes)
pkts bytes target prot opt in out source destination
.... various site specific rules deleted
366 22424 SSHD tcp -- eth1 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
138 9940 ACCEPT tcp -- eth1 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
.... various site specific rules deleted
47996 3742K DROP all -- eth1 * 0.0.0.0/0 0.0.0.0/0 state INVALID,NEW
---
Where the last rule blocks all new connections that haven't been ACCEPTed in the past, ie, a statefull rule.

This is only an example, I've no idea how you set up your iptables. Generally you'll have a line that ACCEPTS ssh(port 22), and the above should go on the line before. Some systems (notably Suse) use custom firewall scripts, see README.suse for contributed instructions.

You will also need to modify your startup script so that sshdfilter is run instead of sshd. sshdfilter will then itself run sshd. sshdfilter V1.4.x will daemonise, so you can run sshdfilter like you'ed run sshd. A sample RedHat style startup script is in the tar file, note only one line has changed from the distribution supplied default.

sshd log messages vary across distributions, so you need to install the correct sshdfilter executable for your sshd. Several are provided, supporting Debian 3.1, Redhat 7.3 to 9.0, Fedora Core 2-4, CentOS, Suse 10.0 RC 1, RedHat Enterprise Linux 4, and the many other distros based on these. If your log messages are too different you can also add your own using those provided as an example.

Copy etc/sshdfilterrc to /etc/ and edit to suit your requirements. You may want to uncomment and properly set $interface, which allows for only blocking (say) the external interface - so even triggering sshdfilter into blocking won't actually do anything if you are ssh'ing from the inside/via VPN.

LogWatch

sshdfilter comes with a LogWatch script that understands sshdfilter output. You need a recent (6.1.x) version of LogWatch to use these LogWatch scripts. sshd logging works as it used to, sshdfilter always logs sshd output regardless of it triggering sshdfilter to do some action. The LogWatch scripts consist of two files, you also need to add 'sshdfilt' to /etc/log.d/conf/services/secure.conf (or similar, try 'grep "sshd " /etc/log.d/conf' to find mentions of sshd, sshdfilt should be there too), to tell the 'secure' parser script about sshdfilter - so the 'secure' parser can ignore sshdfilter messages.

Note the scripts expect to find LogWatch version 6.1.x, anything else and you will have to modify the script. That includes later versions.

http://www.csc.liv.ac.uk/~greg/sshdfilter/index_14.html


Firewalling With Netfilter/Iptables

jbrown 17 February, 2008 11:40 Linux Permalink Trackbacks (0)

Firewalling with netfilter/iptables

By Barry O'Donovan
barry@ihl.ucd.ie
http://www.barryodonovan.com

Introduction

iptables is Linux's firewall which has been a part of the kernel since version 2.4. It is often referred to as a packet filter as it examines each packet transferred in every network connection to, from and within your computer. iptables replaced ipchains in the 2.4 kernel and added many new features including connection tracking (also known as stateful packet filtering). In this article we will use iptables to build simple but effective firewalls for the following scenarios using allow/disallow rules based on IP addresses, ports and states:

  1. a standard home computer;
  2. a home/small office network with a single internet connection;
  3. port forwarding for a home/small office network.

Rules, Targets, Chains, Tables, States and all that jazz

iptables makes decisions on what to do with a packet based on rules that the system administrator creates. Data is passed through the internet in the form of packets of information; connecting from your computer to a website will cause many packets to be exchanged in both directions. A rule specifies the criteria necessary for a packet to match it. A decision is known as a target and it can be a user-defined chain (not covered in this article) or one of the following:

ACCEPT
Allow the packet through the firewall.
DROP
Drops the packet; the packet is not allowed through the firewall and the sender of the packet is not notified.
There a number of other possible targets and we will cover some of these later.

Rules are grouped into chains which in turn are contained in tables. There are three default tables which the packets may traverse; we are only concerned with one of these right now: the filter table. This is the default table and contains three chains:

OUTPUT
For packets generated by and leaving your computer; for example when you connected to the Linux Gazette's web site your browser created a packet and sent it out of your computer to the Gazette's server.
INPUT
Any packets coming into your computer; for example the packets containing the Gazette's web page sent back by its server to your browser.
FORWARD
For packets being routed through your computer; for example entering one network card and leaving through the other. We will cover this in more detail later.

The two other tables available by default are the nat table and the mangle table; we will use nat later for setting up a home network when only one network connection is available.

As I mentioned in the introduction, iptables is capable of stateful packet filtering. This means that we can create rules not only based on IPs and ports but also on whether a packet exists in any of the following states:

NEW
The packet is trying to start a new connection; for example when you first connected to the Linux Gazette website your browser attempted to create a new connection with the Gazette's web server.
ESTABLISHED
A connection that has seen packets travel in both directions; once the Gazette's web server replied to your browser the connection is established.
RELATED
A packet that is starting a new connection but is related to an existing connection. An example of this is downloading a file over FTP. When you first connect to an FTP server you are creating a new connection to its FTP port. However, when you download a file from the FTP server using this connection a second new connection is made between your computer and the FTP server for the file download. Although it is a new connection it is related to the first. This stateful packet filtering is useful as this new connection does not use the FTP port and simple port based rules are not appropriate for this.
INVALID
This packet is associated with no known connection. These packets should be dropped.

Creating and Storing Rules

Rules can be appended to the chains directly by using the iptables command. For example, to add a new rule to allow new connections to a web server running on your computer from anywhere we would execute the following:

$ iptables -A INPUT -s 0/0 -d 1.2.3.4 -m state --state NEW -p tcp --dport 80 -i eth0 -j ACCEPT
where:
-s (or --src or --source) and -d (or --dst or --destination)
is the source and destination specification of the packet. It is usually an IP address with an optional mask. 0/0 is shorthand for 0.0.0.0/0.0.0.0 meaning that the source can be any IP address. 1.2.3.4 is the IP our your machine and is equivalent to writing 1.2.3.4/32 or 1.2.3.4/255.255.255.255 meaning the destination must be this and only this IP. Other examples include:
1.2.3.0/24Any IP in the range 1.2.3.0 to 1.2.3.255 (256 possible IPs). Could also have been written as 1.2.3.0/255.255.255.0
1.2.0.0/16Any IP in the range 1.2.0.0 to 1.2.255.255 (65536 possible IPs). Could also have been written as 1.2.0.0/255.255.0.0
! 1.2.3.0/24The exclamation mark inverts the match so this will result is a match if the IP is anything except one in the given range 1.2.3.0 to 1.2.3.255.
-m state --state NEW
matches only packets that have a status of NEW. This can be anyone of or a comma separated list of the four possible states.
-p tcp
apply this rule to packets using the TCP protocol only. This can be anyone of tcp, udp, icmp or all (default). The exclamation mark can be used to invert the match.
--dport 80 (or --destination-port)
matches a packet trying to connect to port 80. The exclamation mark can be used to invert this match also. A range of ports can be given in the format begin:end.
-i eth0 (or --in-interface eth0)
name of an interface via which a packet is going to be received. Possible interfaces on your computer can be found using the command 'ifconfig'. In this example your computer is connected to the internet through the first (or only) ethernet card.
-j ACCEPT
the target. In this case, if the incoming packet is creating a new TCP connection from anywhere to port 80 on your computer through the first ethernet card, we will allow it through.

Note that in all of the following examples I am assuming that your computer is connected to the internet through an ethernet card. Change eth0 for wifi0, ppp0, etc, as appropriate for your computer. Furthermore I'm assuming that your computer's IP address is 1.2.3.4.

Obviously we do not want to set up the firewall manually everytime we boot the computer. Most Linux distributions will give you the option of having these rules loaded automatically at boot from a file; in fact most distributions will come with a preconfigured firewall as standard. The location of this file will vary from distribution to distribution but it should be easily found by executing 'locate iptables'. For RedHat or Fedora Core users it can be found at /etc/sysconfig/iptables.

The essential elements of this file are:
(I have added the line numbers for explanation - they should not appear in a file intended for use with iptables):

1 # Firewall configuration
2 *filter
3 :INPUT <target> [0:0]
4 :FORWARD <target> [0:0]
5 :OUTPUT <target> [0:0]
6
7 # your rules here
8 
9 COMMIT
Listing 1 - The essential elements of an iptables file

Line 2 of this file tells iptables that the following rules apply to the filter table. The next three lines (3-5) define the default targets for the three chains. We place our rules after these and before COMMIT, which does just that; commits our rules to the firewall.

Each packet traverses the rules of the appropriate chain from the first to the last. If a packet matches a rule then it stops traversing the chain at that rule and its fate is decided by that rule's target. If the packet does not match any rule then its fate is the default target of its chain.

I would recommend using the following skeleton configuration for all your firewalls:

 1 *filter
 2 :INPUT DROP [0:0]
 3 :FORWARD DROP [0:0]
 4 :OUTPUT ACCEPT [0:0]
 5
 6 # allow local loopback connections
 7 -A INPUT -i lo -j ACCEPT
 8
 9 # drop INVALID connections
10 -A INPUT   -m state --state INVALID -j DROP
11 -A OUTPUT  -m state --state INVALID -j DROP
12 -A FORWARD -m state --state INVALID -j DROP
13
14 # allow all established and related
15 -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
16 
17 # add anymore rules here
18
19 COMMIT
Listing 2 - Reccommended skeleton configuration

I've set the default target for the INPUT and FORWARD chains to DROP, while allowing all outgoing connections (lines 2-4). On a standard server or home computer we should not be routing any packets as standard (we will later and we will look at this in more detail then). Any outgoing connections will come from our computer and we can generally assume that they are not a security problem. In contrast, all incoming packets should be treated as a security risk unless we have explicitly allowed them.

Line 7 tells iptables to allow all connections originating from the local loopback network interface. This is used by many applications to connect to local services and you must permit these connections. Lines 10-12 drop all connections with a state of INVALID.

Line 15 should be self explanatory - it allows all incoming established or related connections through the firewall. For a connection to become established or related it must first have had a state of NEW and have been allowed though the firewall via a matching rule (had it not been allowed through it would have been dropped by default and could not result in an established or related connection).

Scenario 1: Standard Home Computer

For the standard user using his/her home computer for internet browsing, e-mail, etc then the above firewall is all that is needed as it allows all connections out while preventing any connections that are not related.

For a more paranoid user that wants to control and log all outgoing connections we might use a firewall configuration such as the following:

 1 *filter
 2 :INPUT DROP [0:0]
 3 :FORWARD DROP [0:0]
 4 :OUTPUT DROP [0:0]
 5
 6 # allow local loopback connections
 7 -A INPUT -i lo -j ACCEPT
 8
 9 # drop INVALID connections
10 -A INPUT   -m state --state INVALID -j DROP
11 -A OUTPUT  -m state --state INVALID -j DROP
12 -A FORWARD -m state --state INVALID -j DROP
13 
14 # allow all established and related
15 -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
16 -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
17
18 # allow connections to my ISP's DNS servers
19 -A OUTPUT -d 2.3.4.10 -m state --state NEW -p udp --dport 53 -o eth0 -j ACCEPT
20 -A OUTPUT -d 2.3.4.11 -m state --state NEW -p udp --dport 53 -o eth0 -j ACCEPT
21
22 # allow outgoing connections to web servers
23 -A OUTPUT -d 0/0 -m state --state NEW -p tcp --dport http -o eth0 -j ACCEPT
24 -A OUTPUT        -m state --state NEW -p tcp --dport https -o eth0 -j ACCEPT
25
26 # allow outgoing mail connections to my ISP's SMTP and POP3 server only
27 -A OUTPUT -d 2.3.4.5 -m state --state NEW -p tcp --dport smtp -o eth0 -j ACCEPT
28 -A OUTPUT -d 2.3.4.5 -m state --state NEW -p tcp --dport pop3 -o eth0 -j ACCEPT
29
30 # log all other attempted out going connections
31 -A OUTPUT -o eth0 -j LOG
32 # default is to DROP out-going connections
33 
34 COMMIT
Listing 3 - Paranoid home user

This configuration denies all connections by default and only allows those we explicitly define rules for. Line 16 adds a second rule based on the established or related rules for outgoing connections. Just as with line 15, this is necessary as the default rule of the OUTPUT chain is DROP. Also note that when we specifying the interface for the OUTPUT chain rules we use -o (or --out-interface) as opposed to -i.

The first rules we have added (lines 19 and 20) are to allow outgoing connections to your ISP's DNS server; I am assuming your ISP has a primary and a secondary DNS server with IPs 2.3.4.10 and 2.3.4.11 respectively. These connections are essential so your computer can convert a domain name (such as www.linuxgazette.net) into its IP address; without that conversion we would not be able to connect to the website. DNS lookups are usually done via the UDP protocol. Unless you are doing anything out of the ordinary this should be sufficient.

The next two rules (lines 23 and 24) allow your internet browser to connect to any website using both the normal and the encrypted protocols. You'll notice that I have used http and https to specify the ports here instead of 80 and 443. This makes the rules more readable and you can substitute the service name for any port so long as it appears in the file /etc/services. You should also notice that in the second rule I omitted the destination IP mask; this is equivalent to writing "match any destination IP" (-d 0/0). Lastly, I could have turned these two rules into one using:
-A OUTPUT -m state --state NEW -p tcp -m multiport --dport http,https -o eth0 -j ACCEPT

Another standard operation that a home computer would be used for is e-mailing. E-mailing requires two services: SMTP to send mail and POP3 (or IMAP in some cases) to receive mail. I have added a rule for each of these (lines 27 and 28) where I am assuming that your ISP uses the same server for both (2.3.4.5). In most cases your ISP will not give you the IPs of its mail servers, but instead their domain names; e.g. mail.my-isp.com. We can rewrite these rules using this as follows:
-A OUTPUT -d mail.my-isp.com -m state --state NEW -p tcp --dport smtp -o eth0 -j ACCEPT
-A OUTPUT -d mail.my-isp.com -m state --state NEW -p tcp --dport pop3 -o eth0 -j ACCEPT

It is generally a better idea to use IPs wherever possible.

The final rule has a target we have not come across yet: the LOG target. This logs the details of a matching packet. You can review the log with the dmesg command or via syslogd. Some distributions have a utility called logwatch which will format these reports into an e-mail sent to the root account. The LOG target is a non-terminating target; the packet will continue traversing the chain. So in the above example we log all outgoing packets that have not matched one of the rules, that packet continues traversing the chain and as there are no other rules, the default target for the OUTPUT chain is used (DROP).

If you use any other services, such as Jabber, IRC, file sharing clients, etc, you will have to add rules for these also. Just follow the above example. If you don't know what ports to open and you can't find it in /etc/services, then add a logging rule at the beginning of the rules, e.g.
-A OUTPUT -i eth0 -j LOG
and examine the output of the command dmesg (look for the destination port, DPT=???). I also feel I should mention that filtering the OUTPUT chain in this manner can be quite problematic; you might find some programs hanging or freezing while they try and establish connections you never thought of allowing, or using the UDP protocol instead of the TCP, etc. Unless you really want or need to lock the OUTPUT chain down, it might be just as easy to set the default rule to ACCEPT and then block the outgoing connections on a case by case basis.

Scenario 2: Home Network with a Single Connection

Most home users and small offices connect to the internet via a single dial-up, ISDN or broadband (DSL) connection. This scenario covers the problem: "I only have a single network connection, but I would like all my computers to have internet access. How is this possible?" The examples in this scenario will enable you to set up a home or office network using your networked computer as a gateway for your other computers.

Home Network - Scenario 2
Figure 1 - Author's Home Network

My own situation is depicted in Figure 1; I have a single broadband connection with a static IP address (1.2.3.4) connected to eth0. My second ethernet card (eth1) is a wireless PCI card. In my home there are two laptops, each also with wireless cards built in.

The first issue is that every computer on the internet needs to be uniquely identifiable by an IP address. Irrespective of whether you have a dial-up or a broadband connection, you will only have been assigned one IP address. This can either be static (some broadband ISPs will allocate you a single IP that will not change) or dynamic (you will be assigned different IPs every time you reconnect to the network). When you send out a packet it includes the destination address and the source address. Although we can send a packet with any source address, only replies to ones with your source address will return to you.

Now we must assign an IP to every network interface on the network. In the case of eth0, it was assigned by my ISP. But what IPs will we give the wireless interface and the laptops? ICANN (Internet Corporation For Assigned Names and Numbers) has assigned certain blocks of IPs for use in private networks. One of these blocks is given by the IP mask 192.168.0.0/255.255.0.0. Which IPs of this set you choose to use is entirely up to you. As you can see from Figure 1, I have assigned 192.168.0.1 to my wireless PCI card, and 192.168.0.2 and 192.168.0.3 to the laptops.

The nat (network address translation) table of iptables allows us to use one IP address for many different computers and works as follows: if the first laptop tries to connect to a website it sends a packet with the source address of 192.168.0.2 to eth1 of the networked computer. The networked computer will then forward this packet from eth1 to eth0. Just before the packet is transmitted, the nat table will change the source address from 192.168.0.2 to 1.2.3.4. iptables will automatically remember that it did this and when the reply packets arrive with a destination of 1.2.3.4 and change it to 192.168.0.2, routing it through eth1 to the laptop.

Let's begin with the firewall configuration:

 1 *filter
 2 :INPUT DROP [0:0]
 3 :FORWARD DROP [0:0]
 4 :OUTPUT DROP [0:0]
 5
 6 # allow local loopback connections
 7 -A INPUT -i lo -j ACCEPT
 8 
 9 # drop INVALID connections
10 -A INPUT   -m state --state INVALID -j DROP
11 -A OUTPUT  -m state --state INVALID -j DROP
12 -A FORWARD -m state --state INVALID -j DROP
13
14 # allow all established and related
15 -A INPUT   -m state --state ESTABLISHED,RELATED -j ACCEPT
16 -A OUTPUT  -m state --state ESTABLISHED,RELATED -j ACCEPT
17 -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
18 
19 # allow connections to my ISP's DNS servers
20 -A OUTPUT  -d 2.3.4.10 -m state --state NEW -p udp --dport 53 -o eth0 -j ACCEPT
21 -A OUTPUT  -d 2.3.4.11 -m state --state NEW -p udp --dport 53 -o eth0 -j ACCEPT
22 -A FORWARD -d 2.3.4.10 -m state --state NEW -p udp --dport 53 -i eth1 -o eth0 -j ACCEPT
23 -A FORWARD -d 2.3.4.11 -m state --state NEW -p udp --dport 53 -i eth1 -o eth0 -j ACCEPT
24 
25 # allow outgoing connections to web servers
26 -A OUTPUT  -d 0/0 -m state --state NEW -p tcp -m multiport --dport http,https -o eth0 -j ACCEPT
27 -A FORWARD -d 0/0 -m state --state NEW -p tcp -m multiport --dport http,https -o eth0 -i eth1 -j ACCEPT
28 
29 # allow outgoing mail connections to my ISP's SMTP and POP3 server only
30 -A OUTPUT  -d mail.my-isp.com -m state --state NEW -p tcp -m multiport --dport smtp,pop3 -o eth0 -j ACCEPT
31 -A FORWARD -d mail.my-isp.com -m state --state NEW -p tcp -m multiport --dport smtp,pop3 -o eth0 -j ACCEPT
32
33 # log all other attempted out going connections
34 -A OUTPUT -o eth0 -j LOG
35 -A FORWARD -j LOG
36 # default is to DROP out-going connections
37 
38 COMMIT
39 
40 *nat
41 
42 # set up IP forwarding and nat
43 -A POSTROUTING -o eth0 -j SNAT --to 1.2.3.4
44 
45 COMMIT
Listing 4 - Home/office network with NAT

As well as demonstrating NAT, this example also introduces the use of the FORWARD chain. The networked computer is now also working as a router; as well an nat-ing the packets from the laptops, it is also routing them from eth1 to eth0 and vice-versa. As such we have adding another ESTABLISHED,RELATED rule on line 17, this time for the FORWARD chain.

Similarly, on lines 22,23,27,31 and 35, I have added in lines to allow the same connections we were allowing previously to come from the FORWARD chain. However, there is one big security risk here: I have not specified any source address. Anyone within range of the wireless network can assume an unused IP and use your broadband connection. We would prevent this by changing line 27, for example, to:
-A FORWARD -s 192.168.0.2 -d 0/0 -m state --state NEW -p tcp -m multiport --dport http,https -o eth0 -i eth1 -j ACCEPT
-A FORWARD -s 192.168.0.3 -d 0/0 -m state --state NEW -p tcp -m multiport --dport http,https -o eth0 -i eth1 -j ACCEPT
and similarly for the rest of the rules.

The iptables NAT-ing magic happens in the nat table with one rule:
-A POSTROUTING -o eth0 -j SNAT --to 1.2.3.4 It's as simple as that! Almost. IP forwarding is disabled in the kernel by default and you must execute the following to turn it on:
$ echo 1 > /proc/sys/net/ipv4/ip_forward
You can place this line in the iptables startup scripts (usually /etc/rc.d/init.d/iptables) or, preferably, in the /etc/rc.d/rc.local script which is the last script executed during startup.

What if you are using a dynamic IP? Simply change line 43 to:
-A POSTROUTING -o eth0 -j MASQUERADE
This is a special case where the source IP of the outgoing packets are changed to the IP of the outgoing interface; i.e. the IP of eth0. This can be used for a static IP as well but you are advised to use the appropriate version for your set-up.

Using the wireless network as depicted will also require setting the essid and mode parameters of the wireless card. The essid is simply a one-word name for the wireless network. The mode in this example will be Ad-Hoc as opposed to Managed (usually the default) as the network cards are communicating directly as opposed to using a base station. These settings can usually be configured with the following commands:
$ iwconfig eth1 essid barry_home
$ iwconfig eth1 mode Ad-Hoc

(replacing eth1 for eth0, wifi0, etc as appropriate.

This scenario will work just as well if your set-up is a more typical small office set-up as depicted in Figure 2.

Office network (non-wireless)
Figure 2 - Typical small office network

In this case the networked computer is connected to a port on the switch or hub through eth1, and all other office computers are each connected to one of the other ports. The exact same firewall configuration as that in Listing 4 can be used.

Required network settings for this configuration

To be able to access the internet using NAT a number of network configuration settings are required by each computer; the DNS server(s) IP address(es), the gateway IP, subnet mask and an IP address. For the networked computer these will all be supplied by the ISP; let's assume that the ISP provided the following:

IP address:1.2.3.4
Subnet mask:255.255.255.192
Primary DNS:2.3.4.10
Secondary DNS:2.3.4.11
Gateway2.3.4.1

The settings for each of the computers using NAT will then be:

IP address:192.168.0.???
Subnet mask:255.255.255.0
Primary DNS:2.3.4.10
Secondary DNS:2.3.4.11
Gateway192.168.0.1

Note that the gateway for the NAT-ed computers is the second network interface of the networked computer.

Scenario 3: Port forwarding

For the last scenario, let us imagine that instead of hosting your web server on the firewall machine you want to host it on one of the others, say 192.168.0.3. Let us also assume that you're using the Jakarta Tomcat web server which listens on port 8080 by default. Can we use iptables to forward all requests from the firewall to the web server, and to forward all the responses back through the firewall to the originating request? Absolutely and, again, we can do it through the magic that is NAT.

Port forwarding
Figure 3 - Port forwarding

There are two types of NAT; source NAT (SNAT) and destination NAT (DNAT). Scenario 2 used SNAT where we altered the source address of the packets coming from our internal network. This scenario will use DNAT to change the destination address of packets coming into our networked machine from the internet.

This can be accomplished by adding one simple line (44) to our firewall:

40 *nat
41 
42 # set up IP forwarding and nat
43 -A POSTROUTING -o eth0 -j SNAT --to 1.2.3.4
44 -A PREROUTING -i eth0 -p tcp -d 1.2.3.4 --dport 80 -j DNAT --to 192.168.0.3:8080
45
46 COMMIT
Listing 5 - Port forwarding

Ensure you have enabled the kernel's IP forwarding when using the nat table. Now all connections originally bound for port 80 on our networked machine will be forwarded to port 8080 of 192.168.0.3.

Last Remarks

One type of connection we did not cover was 'pings'. If you are running a server it is generally a good idea to allow echo-requests pings through the firewall using the following rule:
-A INPUT -p icmp --icmp-type echo-request -j ACCEPT
You can use the -s option to limit the source IPs that are allowed to ping your machine.

Lastly, a common misconception among many people is that a firewall is "the last line of defence". It is not. It is only the first line of defense in what should be a properly secured, configured and up-to-date machine.

Community Disclaimer

This article is intended as introduction to iptables with practical and useful examples. It is nothing more and nothing less.


Powered by LifeType
© 2006 - Design by Omar Romero (all rights reserved)