Firewall configuration for iptables
- Introduction to the iptables firewall
- Functionality of the firewall in words
- Firewall performance in Linux
- Dropping or rejecting packets?
- Loading the iptables settings
- Adding and removing enemies from the firewall
Introduction to the iptables firewall
A firewall in IT terms is a software or hardware solution which tries to block as much as possible evil network traffic, while allowing useful traffic. Firewalls can be either implemented in existing network devices like routers, as separate devices with the sole function to protect a network or in the first layers of the networking stack on a networked computer.
Firewalls can be very effective in blocking undesirable traffic to your computer or network. This page describes a way to implement a firewall in the kernel of your computer. You should understand that it is just as easy to block enemies, as it is to block your own access. While testing you should always keep a backdoor open, for example by making the firewall not startable when booting. In case you lockout yourself during testing, a reboot will be enough to get access again. Don’t think it won’t happen to you. It happened to me in the past, even with 10+ years experience on Unix/Linux security and firewall configuration.
Iptables is a software solution which is available on most Linux computers with a kernel version 2.4 or newer. To be honest we have to say that iptables is not the firewall itself. The iptables program is a front-end which can be called from the command line to alter filter tables in the kernel. The real firewall is present in the kernel. Because most people will only use the iptables program, it is often referred to as the Linux firewall and we will do it here also for convenience.
Network packets entering or leaving a networked Linux computer pass a number of tables in the kernel. Each table contains zero or more simple rules where the IP address or specific properties of a packet are checked. If a rule matches, a number of actions can be triggered. A network packet can be simply ignored, accepted, rejected or forwarded to another table with rules. Although the principle of this firewall system is simple, it is often quite difficult to setup a decent firewall which matches the need of a specific situation. This is partly because it needs understanding of the underlying structure, and partly because there is no real good documentation which explains the working in such a way that is can be easily understood by people who aren’t a network administrator. There is also the problem that you can solve a firewall problem in many ways with iptables, where many solutions are not optimal because they are either not scalable, flexible or both. I will explain here the framework of the firewall settings I use on some of my networked Linux computers. Those computers are running the Linux distribution Centos 5.4. If you use a different distribution the location of files may be different, but the general concept remains the same.
Functionality of the firewall in words
Most available firewall implementations for iptables miss a decent description in words of the design concepts. They are just presented as a number of rules. This makes it difficult for people to understand the functionality and decide if it fits in their specific situation. It makes it also difficult to make changes to the firewall settings without breaking the functionality. Without additional configuration, five chains of rules are predefined in the kernel. The PREROUTING is the first chain for incoming packets. From the PREROUTING chain, packets can be either forwarded to the INPUT chain or the FORWARD chain. The INPUT chain is for packets which should be delivered locally. The FORWARD chain is only used on computers where routing is enabled. It causes packets to be forwarded to another destination than the local computer. The OUTPUT chain is used to post-process packets which originate from the local computer and the post-routing chain brings the packets to the networking hardware for remote
In our iptables firewall setup we will only concentrate on incoming traffic. Screening outbound traffic may also be needed, for example to block certain applications to connect to remote computers, but in this configuration example we see the outside world as the biggest threat. We will therefore define firewall rules which apply both to the INPUT and FORWARD chain and leave the OUTPUT chain untouched.
First we will divide packets in different groups. Each group of packets deserves its own treatment.
The enemies are packets coming from sources, or going to destinations which are prohibited. One way to recognize an enemy packet is by looking at the source IP address. You may know some IP addresses which are used by spammers who pollute your forums. Enemies can also be packets in which requests are made to suspicious ports, for example to scan your computer for unprotected ports to a running MySQL server.
Friends are packets which are coming from a trusted source. Friends have more privileges than other packets. You could for example allow traffic to the SSH port from friends, but block access from other sources.
- Bogus packets
One method hackers use to attack networked computers is to send them packets which are invalid in the hope that this will either crash the computer, or intercept private traffic. Invalid packets may contain an illegal combination of TCP/IP flags. But it is also possible that hackers send packets to your computer originating from one of the private IP address ranges in the hope they can intercept or disturb some of the traffic. As you may know, some address ranges like 192.168.0.0/16 and 10.0.0.0/8 are reserved for use on private networks. Many standard firewall implementations have exceptions which give more privileges to network traffic from these sources. But if you have a networked computer—especially in a data center—there shouldn’t be any traffic from these IP ranges. These packets must be flagged as bogus and treated accordingly.
- Always allowed packets
Allowed packets are packets where it is absolutely sure that no blocking firewall rule will match them. This can be packets to specific ports where you don’t want any firewall filtering, packets coming from the local loop-back interface or packets parts from a sequence of packets which has been checked and allowed previously. It may seem strange to make a specific group of allowed packets, but it has advantages regarding performance of the firewall.
Firewall performance in Linux
Did I tell you that the firewall implementation of Linux consists of a number of tables with rules which are parsed sequentially? If you understand this concept, you will also understand that the time needed to check a packet against all rules will increase proportional with the number of rules. This can give performance problems on heavily loaded computers where a large number of packets have to be screened per second.
There are a number of ways to solve potential performance problems in the firewall. The first thing that should be done is order the rules in such a way that rules which have the highest chance to match should be in the beginning of the tables. Furthermore we should try to organize the tables for the different types of packets we have defined are processed in such a way that packets which are accepted are accepted in an early stage, while only packets which will probably blocked anyway should traverse the whole chain of tables and rules. To do this, we propose the following flow of packets through the firewall.
Dropping or rejecting packets?
If you don’t allow an incoming packet to your server there are two ways you can react. You can either send a response to the sending server that the packet is rejected, or you could drop the packet and give no answer at all. You will see the rejected response in many firewalls, because it is cleaner according to internet standards, but should we do it just for that reason? If you decide to not accept a packet, there is something wrong with it. Either it is a bogus packet, or it is from a source which you don’t trust. Now lets try to see this in perspective. Compare the computer with your house. If you know that your house is very secure and a burglar is walking around the house looking for a way to enter it, would you then shout “The house is protected” every time he knocks on the door, or would you just say nothing and maybe call the police. Probably the latter. In computers we can do the equivalent of calling the police by writing a log file or sending an email to an administrator address. But if you know the system is secure, you would probably just keep your mouth shut. Not responding to suspicious packets has two other advantages. A burglar knows that the house exists because it is a physical object, but a computer on the internet is only recognizable by its anonymous IP address. If you just ignore all suspicious packets the sender will not know if the computer exists or the IP address is unassigned and the attacker will probably stop after a few attempts to seek a new easier to identify victim. By dropping packets you also have the advantage that it doesn’t take any significant processing power on your side, but due to the way IP protocols work, the process on the sending computer must wait until a timeout eventually signals that the connection failed.
Therefore in our configuration we will only drop packets, not reject them.
Loading the iptables settings
On Red Hat family Linux distributions the configuration of the system which must be loaded at startup is stored in the /etc/sysconfig directory. The file iptables contains a list of rules for the firewall. The framework of our firewall looks like
# # Example fast and scalable firewall configuration with iptables # Please only implement if you fully understand the functionality # because is very easy to lockout yourself from your computer if # the script isn't adapted to your specific situation. # *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] :Always - [0:0] :Allow - [0:0] :Bogus - [0:0] :Enemies - [0:0] :Friends - [0:0] -A INPUT -j Bogus -A INPUT -j Always -A INPUT -j Enemies -A INPUT -j Allow -A FORWARD -j Bogus -A FORWARD -j Always -A FORWARD -j Enemies -A FORWARD -j Allow -A Bogus -p tcp -m tcp --tcp-flags SYN,FIN SYN,FIN -j DROP -A Bogus -p tcp -m tcp --tcp-flags SYN,RST SYN,RST -j DROP -A Bogus -s 169.254.0.0/16 -j DROP -A Bogus -s 172.16.0.0/12 -j DROP -A Bogus -s 192.0.2.0/24 -j DROP -A Bogus -s 192.168.0.0/16 -j DROP -A Bogus -s 10.0.0.0/8 -j DROP -A Bogus -s 127.0.0.0/8 -i ! lo -j DROP -A Always -p udp --dport 123 -j ACCEPT -A Always -m state --state ESTABLISHED,RELATED -j ACCEPT -A Always -i lo -j ACCEPT -A Friends -s 18.104.22.168 -j ACCEPT -A Friends -s 22.214.171.124/24 -j ACCEPT -A Friends -j DROP -A Enemies -m recent --name psc --update --seconds 60 -j DROP -A Enemies -i ! lo -m tcp -p tcp --dport 1433 -m recent --name psc --set -j DROP -A Enemies -i ! lo -m tcp -p tcp --dport 3306 -m recent --name psc --set -j DROP -A Enemies -i ! lo -m tcp -p tcp --dport 8086 -m recent --name psc --set -j DROP -A Enemies -i ! lo -m tcp -p tcp --dport 10000 -m recent --name psc --set -j DROP -A Enemies -s 126.96.36.199 -j DROP -A Allow -p icmp --icmp-type echo-request -j Friends -A Allow -p icmp --icmp-type any -m limit --limit 1/second -j ACCEPT -A Allow -p icmp --icmp-type any -j DROP -A Allow -p tcp -m state --state NEW -m tcp --dport 22 -j Friends -A Allow -p tcp -m state --state NEW -m tcp --dport 25 -j ACCEPT -A Allow -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT -A Allow -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT -A Allow -j DROP COMMIT
I have added a few empty lines to make the file more readable. The first block gives us the names of the chains that we will use, and the initial values of the packet and bytes counters. With these settings we reset the counters each time the firewall configuration is loaded. The default action for the three internal chains INPUT, FORWARD and OUTPUT is set to ACCEPT. This means that the packet is accepted by each of these chains, except when a rule in the chain decides otherwise. The five user defined chains do not have a default action. The firewall logic will therefore continue processing other rules when the rules inside those chains don’t give a definite answer what to do with a packet.
The next two blocks add four filter rules to the INPUT and FORWARD chain. Each rule causes an unconditional jump to a user defined chain. The sequence in which these commands are listed in the iptables file is important, because this will also be the sequence in which the rules will be processed in the kernel.
Bogus network packets
The Bogus group contains a handful of rules which define invalid packets. This includes packets with both the SYN and FIN flags or the SYN and RST flags, packets from specific ranges of invalid IP addresses and packets with IP addresses which belong to the virtual loop-back adapter (127.x.x.x), but which are not from a local source. These are all packets crafted by hackers or attackers to confuse or intrude your system.
High-priority network packets
The second group Always contains high-priority packets which should always be accepted without much delay. In my specific situation, most servers I operate are also acting as NTP time servers. To provide accurate timing, it is necessary to have a low delay when processing networking packets of the Network Time Protocol. These packets are sent as UDP packets to port 123. For this reason these packets are directly accepted, without checking further rules. These packets might originate from an attacker, and even be part of a DDOS attack, but we accept that situation. The processing of NTP packets has such a low overhead that even when packets are coming in at a very high speed, it won’t take too much CPU resources. There are also no states preserved as with the TCP protocol which could cause buffer overflows. The only thing which might happen is saturation of the network, but that would happen with a DDOS attack independent of us accepting or dropping the incoming packets.
The next rule is equally important. It tells us to accept all packets of a connection which has previously been accepted as OK. Many firewall rule sets miss this particular rule, but from a performance point of view it is a very important one. Most TCP/IP protocols exchange a lot of packets over one single connection. If we effectively only test the first packet of that stream and then mark the stream as either accepted or dropped, there is just little overhead involved when processing accepted network connections.
The third rule accepts all traffic to the loop-back adapter. What many firewall rule sets seem to forget is that also traffic to and from the loop-back adapter is screened by the firewall. This can cause severe performance problems if the loop-back adapter is for example used for connections between an Apache web server and an SQL database.
In the next block we define our friends. As you probably have seen, the Friends chain is not called directly from the INPUT or OUTPUT chain. It is called by other rules which we will define later to accept access to specific ports from some IP addresses but not from others. The Friends rule set is called to check the source IP address against a number of possible matches. The first rule checks if the IP address equals 188.8.131.52. If this is the case, the packet is accepted without further processing of firewall rules. It then checks against a block of IP addresses ranging from 184.108.40.206 to 220.127.116.11. If the IP address are in this range, the packet is accepted. The third rule drops all packets which do not match any of the previous IP address matches.
It is obvious that the IP addresses mentioned here are just examples. You should replace them by addresses where friendly traffic to your server is normally coming from, for example the IP address of your home ADSL connection or IP addresses from other servers in your network. You are allowed to add extra rules before the -A Friends -j DROP line.
Enemies can be identified in two ways. Either by their source IP address, or by their behavior. Unfortunately the firewall in the kernel operates at a low level in the networking hierarchy and many behavioral actions are only visible at the application level, for example password dictionary attacks on SSH servers or spam mails. But some behavior can be detected at the lowest networking level and one of this is port scanning. Port scanning is trying to connect to a number of TCP/IP ports of the attacked computer to search for open connections with SQL servers, control panels or other network aware applications. My experience has shown that many port scanners are looking for unprotected connections to MySQL and MSSQL servers. Because my servers only accept SQL requests from local applications, every request from the outside world is by definition an enemy. In the IP firewall I have setup rules which are triggered if requests are coming in from the network for port 1433, port 3306 and some other well known ports. Every IP address from which such a request originates is put in quarantine for a period of 60 seconds. This is done by the –name psc –set and checking with every subsequent packet of the last set action for that IP address was less than 60 seconds ago. The Linux kernel will maintain a list of port-scan IPs which can be accessed at the location /proc/net/ipt_recent/psc. I have used a 60 seconds grace time because in some occasions I might be testing something and accidentally look like a port scanner according to the firewall. In that case I will only lockout myself for a period of one minute. But it is also possible to increase this value to for example 15 minutes or one hour.
Other enemies are enemies which are known by IP address or IP address range. I have added the address 18.104.22.168 as an example, but this could also be ranges like 22.214.171.124/24.
Adding and removing enemies from the firewall
Friends come and go, but enemies accumulate is what Jones’ motto learns us. In IT world this is a little bit different. Many enemies are temporary by nature. These are computers infected by malware, or servers with configuration flaws like open mail or web proxies. Once these problems are fixed, an enemy can turn in a friend again.
Because of this, we are looking for an easy way to add and remove IP addresses or IP ranges to the iptables firewall we have created. In many iptables firewall implementations you will find on the Internet this is very difficult. You have to have a thorough understanding of the working of the firewall before you can make any changes to it. This is strange, because if your computer is under attack, you have probably something else to do than reading manuals, deciphering scripts etc. You have already seen that we have separated enemies from other rules in our firewall by storing all enemy related firewall rules in a separate chain. With two shell scripts we can add or delete rules to this chain.
#!/bin/sh iptables -L -n -v | grep -q $1 RETVAL=$? if [ $RETVAL -eq 0 ]; then echo "IP address already in blocklist: $1" exit 0 fi echo "Adding enemy $1" iptables -A Enemies -s $1 -j DROP
This scripts first makes a dump of the current firewall rules and checks if the IP address or IP range matches an existing rule. If this is the case a warning is shown and the script does nothing. If no matching rule has been found, the IP address or IP range is added to the list of enemies. By checking the whole rules list against the new IP address, we have some basic protection against adding one of our own IPs to the firewall because this script will also fail if it finds the IP address or range in the Friends table. But there is no check of individual IPs against IP ranges so you still have to be careful about what you do.
#!/bin/sh echo "Deleting enemy $1" iptables -D Enemies -s $1 -j DROP
This scripts deletes a rule from the Enemies table which matches the IP address or IP range.
All great discoveries are made by mistake.