Difference between revisions of "Simple stateful firewall"

From ArchWiki
Jump to: navigation, search
m (moved ICMP back into input chain in anticipation of splitting OPEN into TCP and UDP to streamline filtering)
m (split OPEN into TCP and UDP to streamline packet processing and make it easier to follow)
Line 47: Line 47:
 
== Creating necessary chains ==
 
== Creating necessary chains ==
  
For this basic setup, we will create one user-defined chain.
+
For this basic setup, we will create two user-defined chains that we will use to open up ports in the firewall.
  
  # iptables -N OPEN
+
  # iptables -N OPEN-TCP
 +
# iptables -N OPEN-UDP
  
 
== The FORWARD chain ==
 
== The FORWARD chain ==
Line 85: Line 86:
 
  # iptables -A INPUT -p icmp -m state --state NEW -j ACCEPT
 
  # iptables -A INPUT -p icmp -m state --state NEW -j ACCEPT
  
Now we append the OPEN chain to INPUT chain to handle all new incoming connections. Once a connection is accepted by the OPEN chain, it is handled by the RELATED/ESTABLISHED traffic rule
+
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.
  
  # iptables -A INPUT -m state --state NEW -j OPEN
+
  # iptables -A INPUT -p udp -m state --state NEW -j OPEN-UDP
 +
# iptables -A INPUT -p tcp -m state --state NEW -j OPEN-TCP
  
 
Now, with the last two rules, we drop everything that hasn't been explicitly accepted above. For '''TCP''' packets, we deny the connection with a '''tcp-reset'''. Incoming '''UDP''' packets are answered with an '''ICMP''' port unreachable message. All other traffic is answered with a protocol unreachable ICMP message. This way, we imitate Linux's default behaviour:
 
Now, with the last two rules, we drop everything that hasn't been explicitly accepted above. For '''TCP''' packets, we deny the connection with a '''tcp-reset'''. Incoming '''UDP''' packets are answered with an '''ICMP''' port unreachable message. All other traffic is answered with a protocol unreachable ICMP message. This way, we imitate Linux's default behaviour:
Line 99: Line 101:
 
  # iptables -P INPUT DROP
 
  # iptables -P INPUT DROP
  
== The OPEN chain ==
+
== The OPEN chains ==
  
The '''OPEN''' chain contains rules for accepting new incoming connections and streams.  
+
The '''OPEN''' chains contain rules for accepting new incoming TCP connections and UDP streams.
  
The first rule in the OPEN chain will check to make sure that new TCP connections start with a SYN packet.
+
The first rule in the OPEN-TCP chain will check to make sure that new TCP connections start with a SYN packet.
  
 
This is the only invalid TCP flag not covered by the INVALID state. Conntrack keeps track of established connections for 5 days and tracks up to 65536 connections in various states (you can change these values using [[sysctl]]). If you have a very high traffic server, conntrack may become filled and begin to forget about existing connections - causing this rule to drop valid packets. However, for almost all home servers, or peer to peer applications like bittorrent, this rule is fine.
 
This is the only invalid TCP flag not covered by the INVALID state. Conntrack keeps track of established connections for 5 days and tracks up to 65536 connections in various states (you can change these values using [[sysctl]]). If you have a very high traffic server, conntrack may become filled and begin to forget about existing connections - causing this rule to drop valid packets. However, for almost all home servers, or peer to peer applications like bittorrent, this rule is fine.
  
  # iptables -A OPEN -p tcp ! --syn -m state --state NEW -j DROP
+
  # iptables -A OPEN-TCP -p tcp ! --syn -m state --state NEW -j DROP
  
 
If you want to accept ssh connections on every interface, add this rule:
 
If you want to accept ssh connections on every interface, add this rule:
  
  # iptables -A OPEN -p tcp --dport 22 -j ACCEPT
+
  # iptables -A OPEN-TCP -p tcp --dport 22 -j ACCEPT
  
 
You can change the ssh incoming port by editing the configuration file in /etc/ssh/sshd_config as the [[SSH]] wiki refers to.
 
You can change the ssh incoming port by editing the configuration file in /etc/ssh/sshd_config as the [[SSH]] wiki refers to.
Line 130: Line 132:
 
To accept incoming HTTP connections on the interface ppp0:
 
To accept incoming HTTP connections on the interface ppp0:
  
  # iptables -A OPEN -i ppp0 -p tcp --dport 80 -j ACCEPT
+
  # iptables -A OPEN-TCP -i ppp0 -p tcp --dport 80 -j ACCEPT
  
 
To accept all incoming tcp connections with destination ports 65000 to 65005 on interface foo:
 
To accept all incoming tcp connections with destination ports 65000 to 65005 on interface foo:
  
  # iptables -A OPEN -i foo -p tcp --dport 65000:65005 -j ACCEPT
+
  # iptables -A OPEN-TCP -i foo -p tcp --dport 65000:65005 -j ACCEPT
  
 
The same is of course possible with udp:
 
The same is of course possible with udp:
  
  # iptables -A OPEN -i foo -p udp --dport 65000:65005 -j ACCEPT
+
  # iptables -A OPEN-UDP -i foo -p udp --dport 65000:65005 -j ACCEPT
  
 
or with other protocols than tcp and udp:
 
or with other protocols than tcp and udp:

Revision as of 13:28, 12 March 2010

Template:I18n links start Template:I18n entry Template:I18n entry Template:I18n entry Template:I18n links end

Merge-arrows-2.pngThis article or section is a candidate for merging with Iptables .Merge-arrows-2.png

Notes: Talk:Iptables (Discuss in Talk:Simple stateful firewall#)

This page explains how to set up a simple stateful firewall using iptables. It also tries to explain what the rules mean and why they are needed. For simplicity, this HOWTO is split into two parts. The first part deals with a single machine, the second part sets up a NAT gateway.

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 unless you know what you are doing.

Prerequisites

Note: Your kernel needs iptables supports in order to follow this guide. All vanilla Arch Linux kernels have iptables support.

First, install the necessary tools:

# pacman -S iptables

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

# iptables -nvL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

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:

# iptables -P INPUT ACCEPT
# iptables -P FORWARD ACCEPT
# iptables -P OUTPUT ACCEPT
# iptables -F
# iptables -X

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 OPEN-TCP
# iptables -N OPEN-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

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.

The first rule will accept all traffic from the "loopback" (lo) interface, which is necessary for many applications and services. You can add more trusted interfaces here such as "eth1".

# 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.

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.

# iptables -A INPUT -m state --state 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.

# iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

The next rule will accept all new ICMP requests. ICMP means Internet Control Message Protocol. Some ICMP messages are very important, some are less important (like echo requests (pings)), but it is generally a good idea to allow them:

# iptables -A INPUT -p icmp -m state --state 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.

# iptables -A INPUT -p udp -m state --state NEW -j OPEN-UDP
# iptables -A INPUT -p tcp -m state --state NEW -j OPEN-TCP

Now, with the last two rules, we drop everything that hasn't been explicitly accepted above. For TCP packets, we deny the connection with a tcp-reset. Incoming UDP packets are answered with an ICMP port unreachable message. All other traffic is answered with a protocol unreachable ICMP message. This way, we imitate Linux's default behaviour:

# iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset 
# iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
# iptables -A INPUT -j REJECT --reject-with icmp-proto-unreachable

We set the policy for the INPUT chain to DROP in case something somehow slips by our rules.

# iptables -P INPUT DROP

The OPEN chains

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

The first rule in the OPEN-TCP chain will check to make sure that new TCP connections start with a SYN packet.

This is the only invalid TCP flag not covered by the INVALID state. Conntrack keeps track of established connections for 5 days and tracks up to 65536 connections in various states (you can change these values using sysctl). If you have a very high traffic server, conntrack may become filled and begin to forget about existing connections - causing this rule to drop valid packets. However, for almost all home servers, or peer to peer applications like bittorrent, this rule is fine.

# iptables -A OPEN-TCP -p tcp ! --syn -m state --state NEW -j DROP

If you want to accept ssh connections on every interface, add this rule:

# iptables -A OPEN-TCP -p tcp --dport 22 -j ACCEPT

You can change the ssh incoming port by editing the configuration file in /etc/ssh/sshd_config as the SSH wiki refers to.

However, it is probably not a good idea to let the world connect to your machine on port 22 unless you run a public server. Therefore, you can limit which machines can connect to ssh daemon (sshd) by modifying the /etc/hosts.allow file :

# Let local users connect via ssh
sshd: 127.0.0.1

# Allow these adress to connect via ssh
sshd: 192.168.0.1
sshd: 172.272.0.32

Note: to block bruteforce attacks check the script.

To accept incoming HTTP connections on the interface ppp0:

# iptables -A OPEN-TCP -i ppp0 -p tcp --dport 80 -j ACCEPT

To accept all incoming tcp connections with destination ports 65000 to 65005 on interface foo:

# iptables -A OPEN-TCP -i foo -p tcp --dport 65000:65005 -j ACCEPT

The same is of course possible with udp:

# iptables -A OPEN-UDP -i foo -p udp --dport 65000:65005 -j ACCEPT

or with other protocols than tcp and udp:

# iptables -A OPEN -i foo -p 123 -j ACCEPT

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

Protection against spoofing attacks

Blocking reserved local addresses incoming from the internet or local network:

  iptables -I INPUT -i eth0 -s 127.0.0.0/8 -j DROP

You can also add the following line to your /etc/sysctl.conf to enable source address verification which is built into Linux kernel itself.

  net.ipv4.conf.all.rp_filter = 1

"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 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.

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

  net.ipv4.icmp_echo_ignore_all = 1

Protection against other attacks

ICMP type match blocking

If your computer is not a router (like most desktops):

  iptables -I OPEN -p icmp --icmp-type redirect -j DROP
  iptables -I OPEN -p icmp --icmp-type router-advertisement -j DROP
  iptables -I OPEN -p icmp --icmp-type router-solicitation -j DROP
  iptables -I OPEN -p icmp --icmp-type address-mask-request -j DROP
  iptables -I OPEN -p icmp --icmp-type address-mask-reply -j DROP

Block nmap's uptime detection

Prevent uptime detection from port scanners like nmap

Add the following line to your /etc/sysctl.conf file

  net.ipv4.tcp_timestamps = 0

Other kernel parameters

Add the following lines to your /etc/sysctl.conf file to prevent certain kinds of attacks:

net.ipv4.conf.all.accept_source_route = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1

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 IP forwarding?

Save the rules with the command:

# /etc/rc.d/iptables save

and make sure your rules are loaded when you 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

Before we start, make sure that the FORWARD chain is empty:

# iptables -F FORWARD

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.

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:

# iptables -A FORWARD -p tcp --tcp-flags SYN,RST 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

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-unreachable
# 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/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:

# 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/255.255.255.0 -o ppp0 -j MASQUERADE

Don't 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:

# 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

The last step is to enable IP Forwarding (if it isn't 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 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

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:

# 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 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:

# 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 ...)

Working with knockd

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 open -s %IP% -p tcp --syn --dport 22 -j ACCEPT
       cmd_timeout   = 10
       stop_command  = /usr/sbin/iptables -D open -s %IP% -p tcp --syn --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 haven't inadvertantly selected commonly used ports, use this port database, and/or your /etc/services file.

Example of an Iptables Script

The script below is an example of implementation of this wiki. It will block any bruteforcing occurrences to SSH protocol (blocks by IP)


It's needed to turn it an executable with the 'chmod' command

 chmod +x iptables_script.sh

then run it as root user

su -c ./iptables_script.sh

or

sudo bash iptables_script.sh


#! /bin/bash

# ----------------------------------------------------------------------------
#
# script to set the iptables firewall
#
#	http://wiki.archlinux.org/index.php/Simple_stateful_firewall_HOWTO
#
# :: blocks bruteforce attacks (useful for securing SSH servers)
#
# ----------------------------------------------------------------------------

# allow local connections (disable for testing purposes only) allow_lo="yes";
# ---------------------------------------------------------------------------- # interfaces (confirm)
localnet="lo"; internet="eth+"; # the ending '+' is a wildcard for matching patterns # will match for example 'eth0' , 'eth1' , etc.. # ----------------------------------------------------------------------------

# stop the iptables /etc/rc.d/iptables stop
# reset iptables rules iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -P OUTPUT ACCEPT iptables -F iptables -X
# create the chains iptables -N open iptables -N interfaces iptables -N BRUTEGUARD
# >> INPUT chain iptables -A INPUT -p icmp -j ACCEPT iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -A INPUT -j interfaces iptables -A INPUT -j open iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable iptables -P INPUT DROP
# set the default policies for OUTPUT and FORWARD chains iptables -P OUTPUT ACCEPT iptables -P FORWARD DROP
# ---------------------------------------------------------------------------- # >> interfaces chain :: allow all connections from these interfaces # [[ "${allow_lo}" == "yes" ]] && iptables -A interfaces -i ${localnet} -j ACCEPT
# ---------------------------------------------------------------------------- # the 'open' chain :: rules for accept incoming external connections (daemons) # iptables -A open -p tcp -m tcp --dport 80 -j ACCEPT # apache/http iptables -A open -p tcp -m tcp --dport 113 -j ACCEPT # oidentd/auth
# accept ssh and avoid bruteforcings with BRUTEGUARD chain iptables -A open -p tcp -m tcp --dport 22 -j BRUTEGUARD iptables -A open -p tcp -m tcp --dport 22 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
# the BRUTEGUARD chain :: block excessive connections (and bruteforcings) iptables -A BRUTEGUARD -m recent --set --name BF iptables -A BRUTEGUARD -m recent --update --seconds 1800 --hitcount 8 --name BF -j LOG \ --log-level info --log-prefix "[BRUTEFORCE ATTEMPT] " iptables -A BRUTEGUARD -m recent --update --seconds 1800 --hitcount 8 --name BF -j DROP
# ---------------------------------------------------------------------------- # Protection against common attacks #
# allow only SYN packets iptables -A INPUT -p tcp ! --syn -m state --state NEW -j DROP # force fragments packets check iptables -A INPUT -f -j DROP # drop incoming malformed XMAS packets iptables -A INPUT -p tcp --tcp-flags ALL ALL -j DROP # drop all NULL packets iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP

# ---------------------------------------------------------------------------- # Hide the computer # (optional) # #
# block reserved private networks incoming from the internet # (this could block access for interesting ip's, iex: your router, or lan computers.. ) # uncomment only if you need it # iptables -I INPUT -i ${internet} -s 10.0.0.0/8 -j DROP # iptables -I INPUT -i ${internet} -s 172.16.0.0/12 -j DROP # iptables -I INPUT -i ${internet} -s 192.168.0.0/16 -j DROP # iptables -I INPUT -i ${internet} -s 127.0.0.0/8 -j DROP
# block PING request iptables -A INPUT -i ${internet} -p icmp --icmp-type echo-request -j DROP
# ICMP type match blocking iptables -I INPUT -p icmp --icmp-type redirect -j DROP iptables -I INPUT -p icmp --icmp-type router-advertisement -j DROP iptables -I INPUT -p icmp --icmp-type router-solicitation -j DROP iptables -I INPUT -p icmp --icmp-type address-mask-request -j DROP iptables -I INPUT -p icmp --icmp-type address-mask-reply -j DROP
# ---------------------------------------------------------------------------- # the end
/etc/rc.d/iptables save
/etc/rc.d/iptables start
# print the iptables iptables -L;

exit 0;

See Also