Difference between revisions of "Simple stateful firewall"

From ArchWiki
Jump to: navigation, search
(SSH bruteforce attacks)
(move to the 21st century)
Line 18: Line 18:
 
This HOWTO assumes that there are currently no iptables rules set. To check this, try the command
 
This HOWTO assumes that there are currently no iptables rules set. To check this, try the command
  
  # iptables -nvL
+
  # iptables-save
  Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
+
  *filter
  pkts bytes target    prot opt in    out    source              destination       
+
:INPUT ACCEPT [0:0]
   
+
  :FORWARD ACCEPT [0:0]
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
+
  :OUTPUT ACCEPT [0:0]
  pkts bytes target    prot opt in    out    source              destination       
+
COMMIT
+
  Chain OUTPUT (policy ACCEPT 0K packets, 0 bytes)
+
  pkts bytes target    prot opt in    out    source              destination       
+
  
If the output looks like the above (except the packet counters), there are no rules set and you are good to go. If not, reset the tables like this:
+
If the output shows no lines beginning with a dash, there are no rules set and you are good to go. If not, put the five lines shown here into a file (e.g. <tt>empty.rules</tt>, and reset the tables like this:
  
  # iptables -P INPUT ACCEPT
+
  # iptables-restore <empty.rules
# iptables -P FORWARD ACCEPT
+
# iptables -P OUTPUT ACCEPT
+
# iptables -F
+
# iptables -X
+
  
 
== Firewall for a single machine ==
 
== Firewall for a single machine ==
Line 60: Line 53:
  
 
First, we set the default policy for the '''INPUT''' chain to '''DROP''' in case something somehow slips by our rules. Dropping all traffic and specifying what is allowed is the best way to make a secure firewall.
 
First, we set the default policy for the '''INPUT''' chain to '''DROP''' in case something somehow slips by our rules. Dropping all traffic and specifying what is allowed is the best way to make a secure firewall.
{{Warning|This is the step where you'll be locked out if you're in logged via ssh.}}
+
{{Warning|This is the step where you will be locked out if you are in logged via ssh.}}
  
 
  # iptables -P INPUT DROP
 
  # iptables -P INPUT DROP
  
Every packet that is received by any network interface will pass the '''INPUT''' chain first. In this chain we make sure that only the packets that we want are accepted.
+
Every packet that is received by any network interface will pass the '''INPUT''' chain first, if it is destined for this machine. In this chain, we make sure that only the packets that we want are accepted.
  
 
The first rule will accept all traffic from the "loopback" (lo) interface, which is necessary for many applications and services.
 
The first rule will accept all traffic from the "loopback" (lo) interface, which is necessary for many applications and services.
Line 72: Line 65:
 
  # iptables -A INPUT -i lo -j ACCEPT
 
  # iptables -A INPUT -i lo -j ACCEPT
  
The second rule will drop all traffic with an "INVALID" state match. Traffic can fall into four "state" categories: NEW, ESTABLISHED, RELATED or INVALID and this is what makes this a "stateful" firewall rather than a less secure "stateless" one. States are tracked using the "conntrack" kernel module which is loaded automatically by iptables.
+
The second rule will drop all traffic with an "INVALID" state match. Traffic can fall into four "state" categories: NEW, ESTABLISHED, RELATED or INVALID and this is what makes this a "stateful" firewall rather than a less secure "stateless" one. States are tracked using the "nf_conntrack_*" kernel modules which are loaded automatically by the kernel as you add rules.
  
{{Note|This rule will drop all packets with invalid headers or checksums, invalid TCP flags, invalid ICMP messages (such as a port unreachable when we did not send anything to the host), and out of sequence packets which can be caused by sequence prediction or other similar attacks. The "DROP" target will drop a packet without any response, contrary to REJECT which politely refuses the packet. We use DROP because there is no proper "REJECT" response to packets that are INVALID, and we don't want to acknowledge that we received these packets.}}
+
{{Note|This rule will drop all packets with invalid headers or checksums, invalid TCP flags, invalid ICMP messages (such as a port unreachable when we did not send anything to the host), and out of sequence packets which can be caused by sequence prediction or other similar attacks. The "DROP" target will drop a packet without any response, contrary to REJECT which politely refuses the packet. We use DROP because there is no proper "REJECT" response to packets that are INVALID, and we do not want to acknowledge that we received these packets.}}
  
  # iptables -A INPUT -m state --state INVALID -j DROP
+
{{Note|ICMPv6 Neighbor Discovery packets remain untracked, and will always be classified "INVALID" though they are not corrupted or thelike. Keep this in mind, and accept them before this rule!}}
 +
 
 +
  # iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
  
 
The third rule will allow traffic that belongs to established connections, or new valid traffic that is related to these connections such as ICMP errors, or echo replies (the packets a host returns when pinged). '''ICMP''' stands for '''Internet Control Message Protocol'''. Some ICMP messages are very important and help to manage congestion and MTU, and are accepted by this rule.
 
The third rule will allow traffic that belongs to established connections, or new valid traffic that is related to these connections such as ICMP errors, or echo replies (the packets a host returns when pinged). '''ICMP''' stands for '''Internet Control Message Protocol'''. Some ICMP messages are very important and help to manage congestion and MTU, and are accepted by this rule.
  
  # iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
+
  # iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
  
 
The next rule will accept all new incoming '''ICMP echo requests''', also known as pings. Only the first packet will count as NEW, the rest will be handled by the RELATED,ESTABLISHED rule. Since the computer is not a router, no other ICMP traffic with state NEW should needs to be allowed.
 
The next rule will accept all new incoming '''ICMP echo requests''', also known as pings. Only the first packet will count as NEW, the rest will be handled by the RELATED,ESTABLISHED rule. Since the computer is not a router, no other ICMP traffic with state NEW should needs to be allowed.
  
  # iptables -A INPUT -p icmp --icmp-type 8 -m state --state NEW -j ACCEPT
+
  # iptables -A INPUT -p icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT
  
 
Now we append the OPEN chains to INPUT chain to handle all new incoming connections. Once a connection is accepted by the OPEN chains, it is handled by the RELATED/ESTABLISHED traffic rule. The OPEN chains will either accept new incoming connections, or politely reject them. New TCP connections must be started with SYN packets.
 
Now we append the OPEN chains to INPUT chain to handle all new incoming connections. Once a connection is accepted by the OPEN chains, it is handled by the RELATED/ESTABLISHED traffic rule. The OPEN chains will either accept new incoming connections, or politely reject them. New TCP connections must be started with SYN packets.
  
{{Note| NEW but not SYN is the only invalid TCP flag not covered by the INVALID state. The reason is because they are rarely malicious packets, and they shouldn't just be dropped. Instead, we simply don't accept them, so they are rejected with a TCP RST by the next rule.}}
+
{{Note| NEW but not SYN is the only invalid TCP flag not covered by the INVALID state. The reason is because they are rarely malicious packets, and they should not just be dropped. Instead, we simply do not accept them, so they are rejected with a TCP RST by the next rule.}}
  
  # iptables -A INPUT -p udp -m state --state NEW -j UDP
+
  # iptables -A INPUT -p udp -m conntrack --ctstate NEW -j UDP
  # iptables -A INPUT -p tcp --syn -m state --state NEW -j TCP
+
  # iptables -A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP
  
We reject TCP connections with TCP RST packets and UDP streams with ICMP port unreachable messages if the ports are not opened. This imitates default linux behavior (RFC compliant) and it allows the sender to quickly close the connection and clean up.
+
We reject TCP connections with TCP RST packets and UDP streams with ICMP port unreachable messages if the ports are not opened. This imitates default Linux behavior (RFC compliant), and it allows the sender to quickly close the connection and clean up.
  
 
  # iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreach
 
  # iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreach
Line 116: Line 111:
 
  # iptables -A UDP -p udp --dport 53 -j ACCEPT
 
  # iptables -A UDP -p udp --dport 53 -j ACCEPT
  
See man iptables for more advanced rules, like matching multiple ports.
+
See `<tt>man iptables</tt>` for more advanced rules, like matching multiple ports.
  
 
==== Port Knocking ====
 
==== Port Knocking ====
 +
 +
(xtables-addons ships with xt_pknock which does not require an extra daemon.)
  
 
knockd is a [http://www.portknocking.org/ port knocking] daemon that can provide an added layer of security to your network.  The knockd [http://www.zeroflux.org/cgi-bin/cvstrac.cgi/knock/wiki wiki] provides three example port knocking configurations.  These configs can be easily altered to intergrate properly with firewall described here.  You should simply substitue the <tt>INPUT</tt> chain specification, with the custom <tt>open</tt> chain used in the firewall.
 
knockd is a [http://www.portknocking.org/ port knocking] daemon that can provide an added layer of security to your network.  The knockd [http://www.zeroflux.org/cgi-bin/cvstrac.cgi/knock/wiki wiki] provides three example port knocking configurations.  These configs can be easily altered to intergrate properly with firewall described here.  You should simply substitue the <tt>INPUT</tt> chain specification, with the custom <tt>open</tt> chain used in the firewall.
Line 133: Line 130:
 
         stop_command  = /usr/sbin/iptables -D TCP -s %IP% -p tcp --dport 22 -j ACCEPT
 
         stop_command  = /usr/sbin/iptables -D TCP -s %IP% -p tcp --dport 22 -j ACCEPT
  
It is wise to randomly select the ports that you use for the knock sequence. [https://www.random.org/nform.html Random.org] can help you generate a selection of ports between 1 and 65535. To check that you haven't inadvertantly selected commonly used ports, use this [https://www.grc.com/PortDataHelp.htm port database], and/or your /etc/services file.
+
It is wise to randomly select the ports that you use for the knock sequence. [https://www.random.org/nform.html Random.org] can help you generate a selection of ports between 1 and 65535. To check that you have not inadvertantly selected commonly used ports, use this [https://www.grc.com/PortDataHelp.htm port database], and/or your <tt>/etc/services</tt> file.
  
 
=== Protection against spoofing attacks ===
 
=== Protection against spoofing attacks ===
  
Blocking reserved local addresses incoming from the internet or local network:
+
Blocking reserved local addresses incoming from the internet or local network is normally done through setting the <tt>rp_filter</tt> sysctl to 1. To do so, add the following line to your <tt>/etc/sysctl.conf</tt> to enable source address verification which is built into Linux kernel itself. The verification by the kernel will handle spoofing better than individual iptables rules for each case.
  
  # iptables -I INPUT -i eth0 -s 127.0.0.0/8 -j DROP
+
  net.ipv4.conf.all.rp_filter=1
  
Alternatively, you can also add the following line to your /etc/sysctl.conf to enable source address verification which is built into Linux kernel itself. The verification by the kernel will handle spoofing better than individual iptables rules for each case.
+
Only when asynchronous routing and/or rp_filter=0 is used, need extra checks be used:
  
  net.ipv4.conf.all.rp_filter = 1
+
# iptables -I INPUT ! -i lo -s 127.0.0.0/8 -j DROP
  
 
=== "Hide" your computer ===
 
=== "Hide" your computer ===
Line 151: Line 148:
 
==== Block Ping Request ====
 
==== Block Ping Request ====
  
A 'Ping' request is an ICMP packet sent to the destination address to ensure connectivity between the devices. If your network works well, you can safely block all ping requests. It is important to note that this doesn't actually hide your computer - any packet sent to you is rejected, so you will still show up in a simple nmap "ping scan" of an IP range. However, it will make you less easily detected.
+
A 'Ping' request is an ICMP packet sent to the destination address to ensure connectivity between the devices. If your network works well, you can safely block all ping requests. It is important to note that this ''does not'' actually hide your computer — any packet sent to you is rejected, so you will still show up in a simple nmap "ping scan" of an IP range.
  
To block echo requests, add the following line to your /etc/sysctl.conf file:
+
To block echo requests, add the following line to your <tt>/etc/sysctl.conf</tt> file:
  
 
  net.ipv4.icmp_echo_ignore_all = 1
 
  net.ipv4.icmp_echo_ignore_all = 1
  
==== Tricking portscanners ====
+
==== Tricking port scanners ====
{{Note|This section is incomplete, don't use it yet}}
+
{{Note|This section is incomplete, do not use it yet}}
  
Portscans are used by attackers to identify open ports on your computer. This allows them to identify and fingerprint your running services and possibly launch exploits against them.
+
Port scans are used by attackers to identify open ports on your computer. This allows them to identify and fingerprint your running services and possibly launch exploits against them.
  
 
The INVALID state rule will take care of every type of port scan except UDP, ACK and SYN scans (-sU, -sA and -sS in nmap respectively).  
 
The INVALID state rule will take care of every type of port scan except UDP, ACK and SYN scans (-sU, -sA and -sS in nmap respectively).  
Line 166: Line 163:
 
''ACK scans'' are not used to identify open ports, but to identify ports filtered by a firewall. Due to the SYN check for all TCP connections with the state NEW, every single packet sent by an ACK scan will be correctly rejected by a TCP RST packet. Some firewalls drop these packets instead, and this allows an attacker to map out the firewall rules.
 
''ACK scans'' are not used to identify open ports, but to identify ports filtered by a firewall. Due to the SYN check for all TCP connections with the state NEW, every single packet sent by an ACK scan will be correctly rejected by a TCP RST packet. Some firewalls drop these packets instead, and this allows an attacker to map out the firewall rules.
  
The recent module can be used to trick the remaining two types of port scans. The recent module is used to add hosts to a "recent" list which can be used to fingerprint and stop certain types of attacks. Current recent lists can be viewed in ''/proc/net/ipt_recent/''.
+
The recent module can be used to trick the remaining two types of port scans. The recent module is used to add hosts to a "recent" list which can be used to fingerprint and stop certain types of attacks. Current recent lists can be viewed in <tt>/proc/net/xt_recent/</tt>.
  
 
===== SYN scans =====
 
===== SYN scans =====
Line 174: Line 171:
 
The recent module can be used to keep track of hosts with rejected connection attempts and return a TCP RST for any SYN packet they send to open ports as if the port was closed. If an open port is the first to be scanned, a SYN ACK will still be returned, so running applications such as ssh on non-standard ports is required for this to work consistently.
 
The recent module can be used to keep track of hosts with rejected connection attempts and return a TCP RST for any SYN packet they send to open ports as if the port was closed. If an open port is the first to be scanned, a SYN ACK will still be returned, so running applications such as ssh on non-standard ports is required for this to work consistently.
  
First, insert a rule at the top of the TCP chain. This rule responds with a TCP RST to any host that got onto the TCP-PORTSCAN list in the past twenty seconds. The --update switch causes the recent list to be updated, meaning the 20 second counter is reset.
+
First, insert a rule at the top of the TCP chain. This rule responds with a TCP RST to any host that got onto the TCP-PORTSCAN list in the past twenty seconds. The <tt>--update</tt> switch causes the recent list to be updated, meaning the 20 second counter is reset.
  
 
  # iptables -I OPEN-TCP -p tcp -m recent --update --seconds 60 --name TCP-PORTSCAN -j REJECT --reject-with tcp-rst
 
  # iptables -I OPEN-TCP -p tcp -m recent --update --seconds 60 --name TCP-PORTSCAN -j REJECT --reject-with tcp-rst
Line 185: Line 182:
 
===== UDP scans =====
 
===== UDP scans =====
  
UDP port scans are similar to TCP SYN scans except that UDP is a "connectionless" protocol. There are no handshakes or acknowledgements of a successful connection. Instead, the scanner sends UDP packets to each UDP port. Closed ports should return ICMP port unreachable messages, and open ports do not return a response. Since UDP is not a "reliable" protocol, the scanner has no way of knowing if packets were lost, and has to do multiple checks for each port that does not return a response.
+
UDP port scans are similar to TCP SYN scans except that UDP is a "connectionless" protocol. There are no handshakes or acknowledgements. Instead, the scanner sends UDP packets to each UDP port. Closed ports should return ICMP port unreachable messages, and open ports do not return a response. Since UDP is not a "reliable" protocol, the scanner has no way of knowing if packets were lost, and has to do multiple checks for each port that does not return a response.
  
The linux kernel sends out ICMP port unreachable messages very slowly, so a full UDP scan against a linux machine would take over 10 hours. However, common ports could still be identified, so applying the same countermeasures against UDP scans as SYN scans is a good idea.
+
The Linux kernel sends out ICMP port unreachable messages very slowly{{cite}}, so a full UDP scan against a Linux machine would take over 10 hours. However, common ports could still be identified, so applying the same countermeasures against UDP scans as SYN scans is a good idea.
  
 
First, add a rule to reject packets from hosts on the UDP-PORTSCAN list to the top of the OPEN-UDP chain.
 
First, add a rule to reject packets from hosts on the UDP-PORTSCAN list to the top of the OPEN-UDP chain.
Line 210: Line 207:
 
The ruleset is now finished and should be saved to your hard drive so that it can be loaded on every boot.
 
The ruleset is now finished and should be saved to your hard drive so that it can be loaded on every boot.
  
The configuration file at '''/etc/conf.d/iptables''' points to the location where the rule configuration will be saved.
+
The configuration file at <tt>/etc/conf.d/iptables</tt> points to the location where the rule configuration will be saved.
  
 
<pre>
 
<pre>
Line 218: Line 215:
 
IPTABLES_CONF=/etc/iptables/iptables.rules
 
IPTABLES_CONF=/etc/iptables/iptables.rules
 
IP6TABLES_CONF=/etc/iptables/ip6tables.rules
 
IP6TABLES_CONF=/etc/iptables/ip6tables.rules
IPTABLES_FORWARD=0  # enable IP forwarding?
+
IPTABLES_FORWARD=0  # enable IPv4 forwarding?
 
</pre>
 
</pre>
  
Line 225: Line 222:
 
  # /etc/rc.d/iptables save
 
  # /etc/rc.d/iptables save
  
and make sure your rules are loaded on boot by editing '''/etc/rc.conf''', iptables should be added preferably before 'network'.
+
and make sure your rules are loaded on boot by editing <tt>/etc/rc.conf</tt>, iptables should be added preferably before 'network'.
  
 
   DAEMONS=(... iptables network ...)
 
   DAEMONS=(... iptables network ...)
Line 246: Line 243:
 
Setting up the '''FORWARD''' chain is similar to the '''INPUT''' chain in the first section.
 
Setting up the '''FORWARD''' chain is similar to the '''INPUT''' chain in the first section.
  
Some networks and servers block '''ICMP''' messages and are preventing '''path MTU detection'''. If your outgoing interface's MTU is lower than the MTU on the local network (like with PPPoE connections), this may prevent you from communicating with such servers. The first rule works around this problem, at least for TCP connections:
+
Now we set up a rule with the '''conntrack''' match, identical to the one in the '''INPUT''' chain:
 
+
# iptables -A FORWARD -p tcp --syn -j TCPMSS --clamp-mss-to-pmtu
+
 
+
Now we set up a rule with the '''state''' match, identical to the one in the '''INPUT''' chain:
+
  
  # iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
+
  # iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
  
 
The next step is to enable forwarding for trusted interfaces and to make all packets pass the '''fw-open''' chain.
 
The next step is to enable forwarding for trusted interfaces and to make all packets pass the '''fw-open''' chain.
Line 274: Line 267:
 
==== Setting up the POSTROUTING chain ====
 
==== Setting up the POSTROUTING chain ====
  
Now, we have to define who is allowed to connect to the internet. Let's assume we have the subnet '''192.168.0.0/255.255.255.0''' (which means all addresses that are of the form 192.168.0.*) on '''eth0'''. We first need to accept the machines on this interface in the FORWARD table, that's why we created the '''fw-interfaces''' chain above:
+
Now, we have to define who is allowed to connect to the internet. Let's assume we have the subnet '''192.168.0.0/24''' (which means all addresses that are of the form 192.168.0.*) on '''eth0'''. We first need to accept the machines on this interface in the FORWARD table, that is why we created the '''fw-interfaces''' chain above:
  
 
  # iptables -A fw-interfaces -i eth0 -j ACCEPT
 
  # iptables -A fw-interfaces -i eth0 -j ACCEPT
Line 280: Line 273:
 
Now, we have to alter all outgoing packets so that they have our public IP address as the source address, instead of the local LAN address. To do this, we use the '''MASQUERADE''' target:
 
Now, we have to alter all outgoing packets so that they have our public IP address as the source address, instead of the local LAN address. To do this, we use the '''MASQUERADE''' target:
  
  # iptables -t nat -A POSTROUTING -s 192.168.0.0/255.255.255.0 -o ppp0 -j MASQUERADE
+
  # iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o ppp0 -j MASQUERADE
  
Don't forget the '''-o ppp0''' parameter above, if you omit it, your network will be screwed up.
+
Do not forget the '''-o ppp0''' parameter above, if you omit it, your network will be screwed up.
  
Let's assume we have another subnet, '''10.3.0.0/255.255.0.0''' (which means all addresses 10.3.*.*) on interface '''eth1'''. We add the same rules as above again:
+
Let's assume we have another subnet, '''10.3.0.0/16''' (which means all addresses 10.3.*.*), on the interface '''eth1'''. We add the same rules as above again:
  
 
  # iptables -A fw-interfaces -i eth1 -j ACCEPT
 
  # iptables -A fw-interfaces -i eth1 -j ACCEPT
  # iptables -t nat -A POSTROUTING -s 10.3.0.0/255.255.0.0 -o ppp0 -j MASQUERADE
+
  # iptables -t nat -A POSTROUTING -s 10.3.0.0/16 -o ppp0 -j MASQUERADE
  
The last step is to enable IP Forwarding (if it isn't already enabled):
+
The last step is to enable IP Forwarding (if it is not already enabled):
  
  # echo 1 > /proc/sys/net/ipv4/ip_forward
+
  # echo 1 >/proc/sys/net/ipv4/ip_forward
  
 
Machines from these subnets can now use your new NAT machine as their gateway. Note that you may want to set up a DNS and DHCP server like '''dnsmasq''' or a combination of '''bind''' and '''dhcpd''' to simplify network settings DNS resolution on the client machines. This is not the topic of this HOWTO.
 
Machines from these subnets can now use your new NAT machine as their gateway. Note that you may want to set up a DNS and DHCP server like '''dnsmasq''' or a combination of '''bind''' and '''dhcpd''' to simplify network settings DNS resolution on the client machines. This is not the topic of this HOWTO.
Line 297: Line 290:
 
==== Setting up the PREROUTING chain ====
 
==== Setting up the PREROUTING chain ====
  
Sometimes, we want to forward an incoming connection from the gateway to a LAN machine. To do this, we use the '''fw-open''' chain defined above, as well as the '''PREROUTING''' chain in the '''nat''' table
+
Sometimes, we want to change the address of an incoming packet from the gateway to a LAN machine. To do this, we use the '''fw-open''' chain defined above, as well as the '''PREROUTING''' chain in the '''nat''' table
  
I will give two simple examples: First, we want to forward all incoming SSH connections (port 22) to the ssh server in the machine '''192.168.0.5''':
+
I will give two simple examples: First, we want to change all incoming SSH packets (port 22) to the ssh server in the machine '''192.168.0.5''':
  
 
  # iptables -A fw-open -d 192.168.0.5 -p tcp --dport 22 -j ACCEPT
 
  # iptables -A fw-open -d 192.168.0.5 -p tcp --dport 22 -j ACCEPT
 
  # iptables -t nat -A PREROUTING -i ppp0 -p tcp --dport 22 -j DNAT --to 192.168.0.5
 
  # iptables -t nat -A PREROUTING -i ppp0 -p tcp --dport 22 -j DNAT --to 192.168.0.5
  
The second example will show you how to forward packets to a different port than the incoming port. We want to forward any incoming connection on port '''8000''' to our web server on '''192.168.0.6''', port '''80''':
+
The second example will show you how to change packets to a different port than the incoming port. We want to change any incoming connection on port '''8000''' to our web server on '''192.168.0.6''', port '''80''':
  
 
  # iptables -A fw-open -d 192.168.0.6 -p tcp --dport 80 -j ACCEPT
 
  # iptables -A fw-open -d 192.168.0.6 -p tcp --dport 80 -j ACCEPT

Revision as of 14:09, 23 February 2011

This template has only maintenance purposes. For linking to local translations please use interlanguage links, see Help:i18n#Interlanguage links.


Local languages: Català – Dansk – English – Español – Esperanto – Hrvatski – Indonesia – Italiano – Lietuviškai – Magyar – Nederlands – Norsk Bokmål – Polski – Português – Slovenský – Česky – Ελληνικά – Български – Русский – Српски – Українська – עברית – العربية – ไทย – 日本語 – 正體中文 – 简体中文 – 한국어


External languages (all articles in these languages should be moved to the external wiki): Deutsch – Français – Română – Suomi – Svenska – Tiếng Việt – Türkçe – فارسی

This page explains how to set up a stateful firewall using iptables. It also explains what the rules mean and why they are needed. For simplicity, it is split into two major sections. The first section deals with a firewall for a single machine, the second sets up a NAT gateway in addition to the firewall from the first section.

Warning: The rules are given in the order that they are executed. If you are logged into a remote machine, you may be locked out of the machine while setting up the rules. You should only follow the steps below while you are logged in locally.

Prerequisites

Note: Your kernel needs to be compiled with iptables support. All stock Arch Linux kernels have iptables support.

First, install the userland utilities:

# pacman -S iptables

This HOWTO assumes that there are currently no iptables rules set. To check this, try the command

# iptables-save
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT

If the output shows no lines beginning with a dash, there are no rules set and you are good to go. If not, put the five lines shown here into a file (e.g. empty.rules, and reset the tables like this:

# iptables-restore <empty.rules

Firewall for a single machine

Creating necessary chains

For this basic setup, we will create two user-defined chains that we will use to open up ports in the firewall.

# iptables -N TCP
# iptables -N UDP

The FORWARD chain

If you want to set up your machine as a NAT gateway, please look at the second section of this HOWTO. For a single machine, however, we simply set the policy of the FORWARD chain to DROP and move on:

# iptables -P FORWARD DROP

The OUTPUT chain

We have no intention of filtering any outgoing traffic, as this would make the setup much more complicated and would require some extra thought. In this simple case, we set the OUTPUT policy to ACCEPT.

# iptables -P OUTPUT ACCEPT

The INPUT chain

First, we set the default policy for the INPUT chain to DROP in case something somehow slips by our rules. Dropping all traffic and specifying what is allowed is the best way to make a secure firewall.

Warning: This is the step where you will be locked out if you are in logged via ssh.
# iptables -P INPUT DROP

Every packet that is received by any network interface will pass the INPUT chain first, if it is destined for this machine. In this chain, we make sure that only the packets that we want are accepted.

The first rule will accept all traffic from the "loopback" (lo) interface, which is necessary for many applications and services.

Note: You can add more trusted interfaces here such as "eth1" if you do not want/need the traffic filtered by the firewall.
# iptables -A INPUT -i lo -j ACCEPT

The second rule will drop all traffic with an "INVALID" state match. Traffic can fall into four "state" categories: NEW, ESTABLISHED, RELATED or INVALID and this is what makes this a "stateful" firewall rather than a less secure "stateless" one. States are tracked using the "nf_conntrack_*" kernel modules which are loaded automatically by the kernel as you add rules.

Note: This rule will drop all packets with invalid headers or checksums, invalid TCP flags, invalid ICMP messages (such as a port unreachable when we did not send anything to the host), and out of sequence packets which can be caused by sequence prediction or other similar attacks. The "DROP" target will drop a packet without any response, contrary to REJECT which politely refuses the packet. We use DROP because there is no proper "REJECT" response to packets that are INVALID, and we do not want to acknowledge that we received these packets.
Note: ICMPv6 Neighbor Discovery packets remain untracked, and will always be classified "INVALID" though they are not corrupted or thelike. Keep this in mind, and accept them before this rule!
# iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

The third rule will allow traffic that belongs to established connections, or new valid traffic that is related to these connections such as ICMP errors, or echo replies (the packets a host returns when pinged). ICMP stands for Internet Control Message Protocol. Some ICMP messages are very important and help to manage congestion and MTU, and are accepted by this rule.

# iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

The next rule will accept all new incoming ICMP echo requests, also known as pings. Only the first packet will count as NEW, the rest will be handled by the RELATED,ESTABLISHED rule. Since the computer is not a router, no other ICMP traffic with state NEW should needs to be allowed.

# iptables -A INPUT -p icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT

Now we append the OPEN chains to INPUT chain to handle all new incoming connections. Once a connection is accepted by the OPEN chains, it is handled by the RELATED/ESTABLISHED traffic rule. The OPEN chains will either accept new incoming connections, or politely reject them. New TCP connections must be started with SYN packets.

Note: NEW but not SYN is the only invalid TCP flag not covered by the INVALID state. The reason is because they are rarely malicious packets, and they should not just be dropped. Instead, we simply do not accept them, so they are rejected with a TCP RST by the next rule.
# iptables -A INPUT -p udp -m conntrack --ctstate NEW -j UDP
# iptables -A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP

We reject TCP connections with TCP RST packets and UDP streams with ICMP port unreachable messages if the ports are not opened. This imitates default Linux behavior (RFC compliant), and it allows the sender to quickly close the connection and clean up.

# iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreach
# iptables -A INPUT -p tcp -j REJECT --reject-with tcp-rst

For other protocols, we add a final rule to the INPUT chain to reject all remaining incoming traffic with icmp protocol unreachable messages. This imitates Linux's default behavior.

# iptables -A INPUT -j REJECT --reject-with icmp-proto-unreach

The OPEN chains

The OPEN chains contain rules for accepting new incoming TCP connections and UDP streams to specific ports.

Opening ports to incoming connections

To accept incoming TCP connections on port 80:

# iptables -A TCP -p tcp --dport 80 -j ACCEPT

To accept incoming UDP streams on port 53:

# iptables -A UDP -p udp --dport 53 -j ACCEPT

See `man iptables` for more advanced rules, like matching multiple ports.

Port Knocking

(xtables-addons ships with xt_pknock which does not require an extra daemon.)

knockd is a port knocking daemon that can provide an added layer of security to your network. The knockd wiki provides three example port knocking configurations. These configs can be easily altered to intergrate properly with firewall described here. You should simply substitue the INPUT chain specification, with the custom open chain used in the firewall.

For example:

 [options]
       logfile = /var/log/knockd.log
 [opencloseSSH]
       sequence      = 2222:udp,3333:tcp,4444:udp
       seq_timeout   = 15
       tcpflags      = syn,ack
       start_command = /usr/sbin/iptables -A TCP -s %IP% -p tcp --dport 22 -j ACCEPT
       cmd_timeout   = 10
       stop_command  = /usr/sbin/iptables -D TCP -s %IP% -p tcp --dport 22 -j ACCEPT

It is wise to randomly select the ports that you use for the knock sequence. Random.org can help you generate a selection of ports between 1 and 65535. To check that you have not inadvertantly selected commonly used ports, use this port database, and/or your /etc/services file.

Protection against spoofing attacks

Blocking reserved local addresses incoming from the internet or local network is normally done through setting the rp_filter sysctl to 1. To do so, add the following line to your /etc/sysctl.conf to enable source address verification which is built into Linux kernel itself. The verification by the kernel will handle spoofing better than individual iptables rules for each case.

net.ipv4.conf.all.rp_filter=1

Only when asynchronous routing and/or rp_filter=0 is used, need extra checks be used:

# iptables -I INPUT ! -i lo -s 127.0.0.0/8 -j DROP

"Hide" your computer

If you are running a desktop machine, it might be a good idea to block some incoming requests.

Block Ping Request

A 'Ping' request is an ICMP packet sent to the destination address to ensure connectivity between the devices. If your network works well, you can safely block all ping requests. It is important to note that this does not actually hide your computer — any packet sent to you is rejected, so you will still show up in a simple nmap "ping scan" of an IP range.

To block echo requests, add the following line to your /etc/sysctl.conf file:

net.ipv4.icmp_echo_ignore_all = 1

Tricking port scanners

Note: This section is incomplete, do not use it yet

Port scans are used by attackers to identify open ports on your computer. This allows them to identify and fingerprint your running services and possibly launch exploits against them.

The INVALID state rule will take care of every type of port scan except UDP, ACK and SYN scans (-sU, -sA and -sS in nmap respectively).

ACK scans are not used to identify open ports, but to identify ports filtered by a firewall. Due to the SYN check for all TCP connections with the state NEW, every single packet sent by an ACK scan will be correctly rejected by a TCP RST packet. Some firewalls drop these packets instead, and this allows an attacker to map out the firewall rules.

The recent module can be used to trick the remaining two types of port scans. The recent module is used to add hosts to a "recent" list which can be used to fingerprint and stop certain types of attacks. Current recent lists can be viewed in /proc/net/xt_recent/.

SYN scans

In a SYN scan, the port scanner sends SYN packet to every port. Closed ports return a TCP RST packet, or get dropped by a strict firewall. Open ports return a SYN ACK packet regardless of the presence of a firewall.

The recent module can be used to keep track of hosts with rejected connection attempts and return a TCP RST for any SYN packet they send to open ports as if the port was closed. If an open port is the first to be scanned, a SYN ACK will still be returned, so running applications such as ssh on non-standard ports is required for this to work consistently.

First, insert a rule at the top of the TCP chain. This rule responds with a TCP RST to any host that got onto the TCP-PORTSCAN list in the past twenty seconds. The --update switch causes the recent list to be updated, meaning the 20 second counter is reset.

# iptables -I OPEN-TCP -p tcp -m recent --update --seconds 60 --name TCP-PORTSCAN -j REJECT --reject-with tcp-rst

Next, the rule for rejecting TCP packets need to be modified to add hosts with rejected packets to the TCP-PORTSCAN list.

# iptables -D INPUT -p tcp -j REJECT --reject-with tcp-rst
# iptables -A INPUT -p tcp -m recent --set --name TCP-PORTSCAN -j REJECT --reject-with tcp-rst
UDP scans

UDP port scans are similar to TCP SYN scans except that UDP is a "connectionless" protocol. There are no handshakes or acknowledgements. Instead, the scanner sends UDP packets to each UDP port. Closed ports should return ICMP port unreachable messages, and open ports do not return a response. Since UDP is not a "reliable" protocol, the scanner has no way of knowing if packets were lost, and has to do multiple checks for each port that does not return a response.

The Linux kernel sends out ICMP port unreachable messages very slowlyTemplate:Cite, so a full UDP scan against a Linux machine would take over 10 hours. However, common ports could still be identified, so applying the same countermeasures against UDP scans as SYN scans is a good idea.

First, add a rule to reject packets from hosts on the UDP-PORTSCAN list to the top of the OPEN-UDP chain.

# iptables -I OPEN-UDP -p udp -m recent --update --seconds 60 --name UDP-PORTSCAN -j REJECT --reject-with port-unreach

Next, modify the reject packets rule for UDP:

# iptables -D INPUT -p udp -j REJECT --reject-with icmp-port-unreach
# iptables -A INPUT -p udp -m recent --set --name UDP-PORTSCAN -j REJECT --reject-with icmp-port-unreach

Protection against other attacks

See the TCP/IP stack hardening guide for relevant kernel parameters.

SSH bruteforce attacks

Note: This section is a work in progress.

To ban IP that makes too many password failures you can use Fail2ban, DenyHosts or Sshguard. These update firewall rules to reject the IP address.

Saving the rules

The ruleset is now finished and should be saved to your hard drive so that it can be loaded on every boot.

The configuration file at /etc/conf.d/iptables points to the location where the rule configuration will be saved.

IPTABLES=/usr/sbin/iptables
IP6TABLES=/usr/sbin/ip6tables

IPTABLES_CONF=/etc/iptables/iptables.rules
IP6TABLES_CONF=/etc/iptables/ip6tables.rules
IPTABLES_FORWARD=0  # enable IPv4 forwarding?

Save the rules with the command:

# /etc/rc.d/iptables save

and make sure your rules are loaded on boot by editing /etc/rc.conf, iptables should be added preferably before 'network'.

 DAEMONS=(... iptables network ...)

Setting up a NAT gateway

This section of the HOWTO deals with NAT gateways. It is assumed that you already read the first part of the HOWTO and set up the INPUT, OUTPUT, OPEN and interfaces chains like described above. All rules so far have been created in the filter table. In this section, we will also have to use the nat table.

Setting up the filter table

Creating necessary chains

In our setup, we will use another two chains in the filter table, the fw-interfaces and fw-open chains. Create them with the commands

# iptables -N fw-interfaces
# iptables -N fw-open

Setting up the FORWARD chain

Setting up the FORWARD chain is similar to the INPUT chain in the first section.

Now we set up a rule with the conntrack match, identical to the one in the INPUT chain:

# iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

The next step is to enable forwarding for trusted interfaces and to make all packets pass the fw-open chain.

# iptables -A FORWARD -j fw-interfaces 
# iptables -A FORWARD -j fw-open 

The remaining packets are denied with an ICMP message:

# iptables -A FORWARD -j REJECT --reject-with icmp-host-unreach
# iptables -P FORWARD DROP

Setting up the fw-interfaces and fw-open chains

The meaning of the fw-interfaces and fw-open chains is explained later, when we deal with the POSTROUTING and PREROUTING chains in the nat table, respectively.

Setting up the nat table

All over this section, we assume that the outgoing interface (the one with the public internet IP) is ppp0. Keep in mind that you have to change the name in all following rules if your outgoing interface has another name.

Setting up the POSTROUTING chain

Now, we have to define who is allowed to connect to the internet. Let's assume we have the subnet 192.168.0.0/24 (which means all addresses that are of the form 192.168.0.*) on eth0. We first need to accept the machines on this interface in the FORWARD table, that is why we created the fw-interfaces chain above:

# iptables -A fw-interfaces -i eth0 -j ACCEPT

Now, we have to alter all outgoing packets so that they have our public IP address as the source address, instead of the local LAN address. To do this, we use the MASQUERADE target:

# iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o ppp0 -j MASQUERADE

Do not forget the -o ppp0 parameter above, if you omit it, your network will be screwed up.

Let's assume we have another subnet, 10.3.0.0/16 (which means all addresses 10.3.*.*), on the interface eth1. We add the same rules as above again:

# iptables -A fw-interfaces -i eth1 -j ACCEPT
# iptables -t nat -A POSTROUTING -s 10.3.0.0/16 -o ppp0 -j MASQUERADE

The last step is to enable IP Forwarding (if it is not already enabled):

# echo 1 >/proc/sys/net/ipv4/ip_forward

Machines from these subnets can now use your new NAT machine as their gateway. Note that you may want to set up a DNS and DHCP server like dnsmasq or a combination of bind and dhcpd to simplify network settings DNS resolution on the client machines. This is not the topic of this HOWTO.

Setting up the PREROUTING chain

Sometimes, we want to change the address of an incoming packet from the gateway to a LAN machine. To do this, we use the fw-open chain defined above, as well as the PREROUTING chain in the nat table

I will give two simple examples: First, we want to change all incoming SSH packets (port 22) to the ssh server in the machine 192.168.0.5:

# iptables -A fw-open -d 192.168.0.5 -p tcp --dport 22 -j ACCEPT
# iptables -t nat -A PREROUTING -i ppp0 -p tcp --dport 22 -j DNAT --to 192.168.0.5

The second example will show you how to change packets to a different port than the incoming port. We want to change any incoming connection on port 8000 to our web server on 192.168.0.6, port 80:

# iptables -A fw-open -d 192.168.0.6 -p tcp --dport 80 -j ACCEPT
# iptables -t nat -A PREROUTING -i ppp0 -p tcp --dport 8000 -j DNAT --to 192.168.0.6:80

The same setup also works with udp packets.

Saving the rules

Like above, we have to save the rules. Only this time, we have to enable IP forwarding in /etc/conf.d/iptables:

# Configuration for iptables rules

IPTABLES=/usr/sbin/iptables

IPTABLES_CONF=/etc/iptables/iptables.rules
IPTABLES_FORWARD=1  # enable IP forwarding!!!

Save the rules

# /etc/rc.d/iptables save

and make sure your rules are loaded when you boot

DAEMONS=(... iptables ...)

See Also