A Linux firewall script with ingress and egress filtering
I'm asked very often what voodoo I do with my Linux firewalls. Simply put, none at all, man iptables and some time will make you do the same. Here is a sample well commented script that I encourage you to learn, hack and improve upon.
This isn't an article on how to learn iptables and the Netfilter framework. You can learn all about that here: http://iptables-tutorial.frozentux.net This is a commented sample rc.firewall script so new, and even old iptables-hands can see how network admins like to secure their public production servers.
This HOWTO is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ©2008 Strykar
Packet filtering policies implemented:
Before you start to write a firewall script, make a list of ingress and egress policies you need to implement, yes, on paper. What these may be differ greatly from location to location and are outside the scope of this humble document.
The policies implemented by this are to allow incoming TCP or even UDP connections as the case may require to ports: 22, 25, 110, 995, 6667, 6697, 10000 and 65500. Log completed connections so we have a list of all completed and successful connections. It also only allows outgoing requests to a set of programs based on UID. It also rate-limits SSH and ICMP to my requirements. If you have any comments regarding source-port filtering implemented below, save it. Read some Comer, Tanenbaum and TCP RFCs and come back.
The shell script
#!/bin/sh
##rc.firewall configuration
##Incoming services allowed: 22, 25, 110, 995, 6667, 6697, 10000 and 65500.
##Author - Strykar 15/03/06
##This uses a dynamic flatfile called /etc/hosts.deny as a block list
##You can simply add the offending IPs by hand or have a script insert it here
##Reload the firewall after every addition/removal in this file.
touch /etc/hosts.deny
## Define our interface, static IP and DNS servers to tighten UDP
IP="1.2.3.4"
INT="eth0"
DNS1="4.2.2.1"
DNS2="4.2.2.2"
IPT="/usr/sbin/iptables"
## Flush and delete tables, states and disable forwarding
$IPT -F
$IPT -X
echo "0" > /proc/sys/net/ipv4/ip_forward
## Default chain policies - DROP ALL IN and OUT, ALLOW localhost
$IPT -P INPUT DROP
$IPT -P OUTPUT DROP
$IPT -P FORWARD DROP
$IPT -A INPUT -i lo -j ACCEPT
$IPT -A OUTPUT -o lo -j ACCEPT
$IPT -A INPUT -s $IP -i $INT -j DROP
$IPT -A INPUT -s 127.0.0.1 -i $INT -j DROP
## Log SSH and other SYN|ACKs so we have logs of all successful completed connections
$IPT -N SYNACK
$IPT -A SYNACK -j LOG --log-level debug
$IPT -A SYNACK -j ACCEPT
$IPT -A INPUT -s ! $DNS1 -d ! $IP -m udp -p udp -j LOG --log-level debug \
--log-prefix "UDP PASSED: "
$IPT -A INPUT -s ! $DNS2 -d ! $IP -m udp -p udp -j LOG --log-level debug \
--log-prefix "UDP PASSED: "
$IPT -A OUTPUT -d ! $DNS1 -s ! $IP -m udp -p udp -j LOG --log-level debug \
--log-prefix "UDP PASSED: "
$IPT -A OUTPUT -d ! $DNS2 -s ! $IP -m udp -p udp -j LOG --log-level debug \
--log-prefix "UDP PASSED: "
$IPT -A INPUT -m tcp -p tcp -m state --state NEW -j LOG --log-level debug --log-prefix "NEW CONNECTION: "
## Check states, match and allow sane connections
$IPT -A INPUT -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m state --state NEW -j DROP
$IPT -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
## Allow UID 99 for HTTPD and UID 1003 for IRCD
$IPT -A INPUT -p tcp --dport 80 --sport 1024:65535 -m state --state NEW -j ACCEPT
$IPT -A INPUT -p tcp --dport 443 --sport 1024:65535 -m state --state NEW -j ACCEPT
$IPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 6667 -j ACCEPT
$IPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 6697 -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1003 -p tcp -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 99 -p tcp -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 0 -p tcp -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1009 -p tcp -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 99 -d $DNS1 -p tcp \
--dport 53 --sport 1024:65535 -m state \
--state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1003 -d $DNS1 -p tcp \
--dport 53 --sport 1024:65535 -m state \
--state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 99 -d $DNS1 -p udp \
--dport 53 --sport 1024:65535 -m state \
--state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1003 -d $DNS1 -p udp \
--dport 53 --sport 1024:65535 -m state \
--state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 99 -d $DNS2 -p tcp \
--dport 53 --sport 1024:65535 -m state \
--state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1003 -d $DNS2 -p tcp \
--dport 53 --sport 1024:65535 -m state \
--state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 99 -d $DNS2 -p udp \
--dport 53 --sport 1024:65535 -m state \
--state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1003 -d $DNS2 -p udp \
--dport 53 --sport 1024:65535 -m state \
--state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1003 -p tcp \
--dport 113 -d 0/0 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1003 -p udp \
--dport 113 -d 0/0 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
## Allow incoming/outgoing SMTP and root to do DNS queries
$IPT -A INPUT -p tcp --dport 25 --sport 1024:65535 -m state \
--state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 0 -p tcp --sport 25 \
-m state --state ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -o $INT -m owner --uid-owner 0 -d $DNS1 -p udp \
--dport 53 --sport 1024:65535 \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 0 -d $DNS1 -p tcp \
--dport 53 --sport 1024:65535 \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -o $INT -m owner --uid-owner 0 -d $DNS2 -p udp \
--dport 53 --sport 1024:65535 \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 0 -d $DNS2 -p tcp \
--dport 53 --sport 1024:65535 \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1005 -d $DNS2 -p udp \
--dport 53 --sport 1024:65535 \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1005 -d $DNS1 -p udp \
--dport 53 --sport 1024:65535 \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
## Allow incoming Dovecot PoP server connections
$IPT -A INPUT -p tcp --dport 110 --sport 1024:65535 -m state \
--state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A INPUT -p tcp --dport 995 --sport 1024:65535 -m state \
--state NEW,ESTABLISHED,RELATED -j ACCEPT
## Allow users root, alice and bob to request DNS based on UID
$IPT -A OUTPUT -m owner --uid-owner 0 -p tcp --dport 53 \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1003 -p tcp --dport 1024:65535 \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1005 -p tcp --dport 1024:65535 \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 0 -p udp --dport 53 \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1003 -p udp --dport 1024:65535 \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner 1005 -p udp --dport 1024:65535 \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
## Allow and rate-limit SSH and drop more than 2 connections/min and pass my ISP subnet thru
$IPT -A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
$IPT -A INPUT -p tcp -s 12.88.0.0/255.248.0.0 --dport 22 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A INPUT -p tcp -s 12.96.0.0/255.252.0.0 --dport 22 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -I INPUT -i eth0 -p tcp --dport 22 -m state --state NEW -m recent --set
$IPT -I INPUT -i eth0 -p tcp --dport 22 -m state --state NEW -m recent \
--update --seconds 60 --hitcount 2 -j DROP
## Allow my ISP subnet to access Webmin and the Prada client
$IPT -A INPUT -s 12.88.0.0/255.248.0.0 -p tcp -m state --state NEW -m tcp --dport 10000 -j ACCEPT
$IPT -A INPUT -s 12.96.0.0/255.252.0.0 -p tcp -m state --state NEW -m tcp --dport 10000 -j ACCEPT
$IPT -A INPUT -s 12.88.0.0/255.248.0.0 -p tcp -m state --state NEW -m tcp --dport 65500 -j ACCEPT
$IPT -A INPUT -s 12.96.0.0/255.252.0.0 -p tcp -m state --state NEW -m tcp --dport 65500 -j ACCEPT
## Allow and rate-limit ICMP echo and time exceeded requests
$IPT -A INPUT -j REJECT --reject-with icmp-port-unreachable
$IPT -A INPUT -p icmp --icmp-type 8 -m limit --limit 3/second -j ACCEPT
$IPT -A INPUT -p ICMP --icmp-type 11 -m limit --limit 2/second -j ACCEPT
## Deny using /etc/deny.hosts
for host in `cat /etc/deny.hosts`; do
$IPT -I INPUT -s $host -j DROP
$IPT -I OUTPUT -d $host -j DROP
done
Addendum
A packet filter, even a deep inspection one, is NOT a complete tool for security. Security is an ongoing process of which your packet filter is the first line of defense. You can have the tightest firewall in the world and all it takes is a trojan on a webpage a user browses to from the machine you're trying to secure and all your efforts at securing the machine amount to nought. This is simply a primer to get your juices flowing with the complicated syntax and the wonderful things one can do with the Netfilter framework.
Missing synproxy from OpenBSD's pf? The iplimit patch in patch-o-matic-ng does the job. It allows you to restrict the number of parallel TCP connections from a particular host or network. Also see this article on the Stream Control Transmission Protocol (SCTP).
