Securing your linux system using iptables can be a daunting task. There are some utilities that can help, but when it comes to security a deep understanding is often very useful. Fundamentally, iptables are lists of rules, executed in order, to determine if a packet should be accepted, dropped, or forwarded along. It has some very powerful features which can help you defend your services, log potential attacks, and forward traffic between computers as well as between ports on the same computer. I will cover some of the basics below.
Listing the current rules
Listing and creating iptables rules requires root privileges. I will be
prepending these commands with the sudo
command so that the commands are
executed as root. I recommend using the following command to list the current
iptables settings on your system:
sudo iptables -L -v --line-numbers
You will get a response that looks something like the following:
Chain INPUT (policy DROP 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT all -- lo any anywhere anywhere
2 0 0 ACCEPT all -- any any anywhere anywhere state RELATED,ESTABLISHED
3 2104 116K ACCEPT tcp -- any any anywhere anywhere tcp dpt:80 state NEW
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 308K packets, 163M bytes)
num pkts bytes target prot opt in out source destination
There may or may not be existing firewall rules. There should be at least three "Chains" which are lists of rules that are evaluated for different conditions. The one we will focus on the most in this write up is the INPUT chain which is the basic rule set for internet packets sent to our system. The other chain we will spend a small bit of time looking at is the FORWARD chain which is the chain for packets which should be forwarded along to other computers.
I will not spend time talking about the OUTPUT chain, which contains rules for packets originating from the host computer. For a general purpose computer I tend to keep OUTPUT wide open, but there are many cases where you may want to restrict the traffic a computer can send.
Starting with a clean slate
Before wiping out your iptables rules, you may want to check if any other programs are running which have inserted rules of their own. VPN clients and servers can modify iptables as does docker. It is strongly recommended that you stop these programs as well as any other internet services you don't need.
When you are ready to proceed execute the following command which will open up your computer to all incoming connections:
sudo iptables -P INPUT ACCEPT # By default accept all incoming packets
sudo iptables -P OUTPUT ACCEPT # By default accept all outgoing packets
sudo iptables -P FORWARD ACCEPT # By default accept all forwarded packets
sudo iptables -F # Flush out all existing rules
sudo iptables -X # Delete all existing chains
You are now ready to construct your new rules from a clean slate. I will be adding rules in the order I typically add them. Remember that order is important as these rules are evaluated in order.
Managing localhost connections
I generally do not restrict internet connections from one port to another port
on the same computer. These will tend to use localhost
as the destination
address or 127.0.0.1
. These are all sent over the lo
internet interface,
so we can add a rule to allow these connections using the command below.
sudo iptables -A INPUT -i lo -j ACCEPT
Let's dissect this command. The -A INPUT
option tells iptables to append
this rule to the INPUT
chain. The -i lo
option tells iptables to apply
this rule to packets on the lo
interface, and -j ACCEPT
tells iptables to
jump to the ACCEPT
chain. The ACCEPT
chain is not a chain of rules, like
the input chain, but a virtual chain which tells the system to accept the
packet. Another virtual chain we will be using is the DROP
chain, which is
used to tell the system which packets should be dropped. There are other
virtual chains which we will not cover in this guide.
Managing existing connections
Once a connection has been established, I also generally want packets related to that connection to continue to be accepted by the system. This can be accomplished using the command below.
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
Some of parts of this command should be familiar, however we add two new
argument groups. The -m state
arguments tell iptables to look at the state
of the packet and match packets based on that state. The arguments --state
ESTABLISHED,RELATED
expands on this saying that this rule applies to packets
whose state is ESTABLISHED or RELATED. This will filter out any packets which
are related to an established connection. Finally the -j ACCEPT
arguments
tell iptables to accept these packets.
Allowing ICMP messages
ICMP protocol messages can be very useful. I like to be able to send a quick ping to my hosts to see if they are still on the network. Although not required, I generally accept ICMP packets by issuing the command below.
sudo iptables -A INPUT -p icmp -j ACCEPT
Opening a port to the world
There are several ports, for example ports 80 (HTTP) and 443 (HTTPS), which you may want to open up to anyone on the internet. Doing this is simple.
sudo iptables -A INPUT -p tcp --dport $port_number -m state --state NEW -j ACCEPT
This filters packets which meet the following criteria:
- From
-p tcp
: Filter packets that use TCP protocol - From
--dport $port_number
: Filter packets that are going to port$port_number
(which should be replaced with an integer). - From
-m state --state NEW
: Filter packets that are part of a NEW connection attempt.
If all the criteria are matched these packets are accepted in this example.
To do this for port 80 (HTTP) I would replace $port_number
with 80, as I do
below.
sudo iptables -A INPUT -p tcp --dport 80 -m state --state NEW -j ACCEPT
Whitelisting connections
Another common pattern is to restrict access to a particular port to one or more authorize hosts. This is especially common for database servers. Let's say we would like to restrict connections to our MySQL server. We could do that by setting up a new chain for authorized hosts and route all TCP connections to the MySQL server port (3306) to that chain.
First we need to set up a new chain, which I will call MYSQL_WHITELIST
.
sudo iptables -N MYSQL_WHITELIST
Then we tell iptables to send all new connection requests for port 3306 to that chain.
sudo iptables -A INPUT -p tcp --dport 3306 -m state --state NEW -j MYSQL_WHITELIST
By default we can also add a rule saying new connections to that port should be dropped.
sudo iptables -A INPUT -p tcp --dport 3306 -m state --state NEW -j DROP
Then we can add the ip addresses or ip address ranges which are allowed to
access the MySQL server to the MYSQL_WHITELIST
chain. To add an individual
ip address, such as 192.168.3.25, we write a command like the one below.
sudo iptables -A MYSQL_WHITELIST -s 192.158.3.25 -j ACCEPT
To add a range, such as 10.3.*.* we can use the notation:
sudo iptables -A MYSQL_WHITELIST -s 10.3.0.0/16 -j ACCEPT
Rate limiting connection requests
These days any server on the internet will be subject to automated scanning for potential exploits. In some cases a system will try thousands and thousands of default passwords for a service, and some of these scanning tools will run these scans at very rapid rates. At best these are annoying, at worst they can cause a denial of service or even find a vulnerability in the system. One way to mitigate the potential denial of service is to limit the rate at which new connections can be established to a particular service.
One service prone to such exploitation is SSH (port 22). If you are connected to a remote server over SSH please keep in mind that adding rules to the firewall can affect your connectivity to the remote service. The rules I will detail below should not disrupt your existing connection, but be sure that you can ssh back to your server before you quit the existing shell.
Rate limiting connections to a port is a little more complex than the rules we have explored so far. In the example below I will use port 22, though these rules can apply to any TCP port.
First, we set up a recent packet filter and label all new connection packets coming into port 22 as "ssh" packets using the command below.
sudo iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name ssh --set
Next we apply our filters. I like to log when there are more than 4 connection attempts in 60 seconds. I can do this using the command below.
sudo iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name ssh --rcheck --seconds 60 --hitcount 4 --rttl -j LOG --log-prefix "SSH_brute_force "
I also want to drop packets that match this filter, so I use the command below.
sudo iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name ssh --rcheck --seconds 60 --hitcount 4 --rttl -j DROP
Make sure you add a rule accepted connections on that port which do not meet the excessive traffic criterion.
sudo iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
This will keep any new connection attempts down to 4 every 60 seconds from any
particular host. Both hitcount
and seconds
can be adjusted to match the
needs of your service. This can also be combined with the whitelisting rules
above to allow certain hosts to exceed these limits.
Lock down anything else
There are two ways to reject any other packets not explicitly mentioned in the rules you have added. The one I find preferable is to set a default DROP policy using the command below.
sudo iptables -P INPUT DROP
The other possibility is to append a rule that all packets not otherwise accepted previously be dropped.
sudo iptables -A INPUT -j DROP
Some people add both to make sure their bases are covered, however if you
use the latter rule, you need to remember that any additional rules you append
will not be executed since all the rules are executed in order. You will need
to insert new rules using the iptables -I
command.
Saving the rules
The rules can be saved, in Ubuntu Linux, using the iptables-save
command,
which will write rules in an internal text format to stdout
. You can restore
these rules using the iptables-restore
command. Keep in mind that iptables
rules you set manually will not be restored on a reboot, so you will want to
find a mechanism for restoring the rules when you reboot.
I personally add a script to the /etc/network/if-pre-up.d
directory as
follows.
#!/bin/sh
iptables-restore < /etc/iptables.rules
exit 0
I then store the rules in /etc/iptables.rules
.
What about IPv6?
Unfortunately you have to go through the whole exercise again for IPv6 using
the ip6tables
, ip6tables-save
, and ip6tables-restore
commands.
Fortunately the structure of the commands is exactly the same. There are
currently many internet sites which have set up firewalls for their IPv4
interfaces, but have not done so for their IPv6 interfaces. It behooves you to
do both, especially as IPv6 becomes more common.