Modern Progress
In a Debian update for iptables 1.8.4-1 on 2019-12-04 the release notes included this.
Also, please note that iptables is no longer Priority: important. This
means it is not installed by default in every system. It has been replaced
by nftables.
So if you can’t find iptables
at all, that’s a clue where to look.
Basically the netfilter project.
General Firewall Information
Maybe you don’t really need to use iptables
which is very fancy and
can be somewhat complex. Maybe you need
TCPWrappers which is a lot easier to do
simple things like banning certain services or certain hosts on a
specific machine.
Distro Specific Bloat
Oh god… Red Hat is trying to make iptables obsolete. Thanks a bunch. Here’s an article comparing "iptables vs firewalld".
The bowels of Ubuntu have excreted its own iptables interface replacement. I’m always annoyed at crap like this: "Developed to ease iptables firewall configuration, ufw provides a user friendly way…" Did Ubuntu ask themselves why the iptables devs hate users so much that they went to elaborate lengths to infuse their software with gratuitous unfriendliness? Could it be that they did not do that? Hmm. If you want to check firewalls the Ubuntu way, try this.
sudo ufw status verbose # My god, this is so damn EASY.
Rather than the sickeningly easy process of learning about a superfluous tool and iptables, you can just do this.
sudo ufw disable
Or continue to use iptables
and ignoring seems to work too.
DenyHosts
More Automatic blacklist via DenyHosts. This helps cut down attempts from known ranges without even giving them the chance even at a slow rate. DenyHost website.
Basic Operation Of iptables
Preparation
First you might want to make sure that your kernel has netfilter configured. This is the part of the kernel that actually applies rules and affects the networking traffic. Check with something like:
lsmod | grep iptable
Look for things like nf_conntrack
if you want to do things that
understand the state of traffic as opposed to reacting blindly to each
packet with no context to what other packets may or should have an
associated influence. Also if you’re creating a router, you’ll want to
see some iptable_nat
kind of stuff.
If your kernel is ready for iptables, then you’ll need the userspace
part which is iptables
itself. This sets and manages the rules that
the kernel’s netfilter applies. Check rpm -qi iptables
.
Checking Rules
Before getting to crazy with making your own rules, you’ll want to
know what rules are already there. I used to use iptables -L
, but I
found that to be more confusing than it should be. It lists the rules
in a format that is supposed to be easy to read (I guess) but it
leaves off certain key pieces of information like what interface the
rule applies to. So now I try to use:
iptables -S
This lists the rules as you would need to re-enter them in order to replicate the chains as they currently are. There is also:
iptables-save
This really could more properly be named "iptables-dump" since I don’t think it really saves anything.
Structure
The kernel manages network traffic in different phases which have their own "tables". These are generally mangle, nat, and filter. To see how they work check out this diagram. The main important table for most people doing most things is the filter table since the other two are more for specific traffic organizing.
In the filter table there are usually three default "chains". These
are "INPUT", "OUTPUT", and "FORWARD". The INPUT chain is applied
before traffic is sent to user (non-kernel) software (like your sshd or
web server). The OUTPUT chain is applied after traffic originates from
user processes (like ping
or ssh
clients). The FORWARD chain is
modified in the kernel right away with the traffic not going to or
emerging from any user process.
CentOS
CentOS (and probably the whole Red Hat/Fedora gang) does things like this:
/sbin/service iptables save
This writes the current rule set to /etc/sysconfig/iptables
(making
a backup at /etc/sysconfig/iptables.save
or ...old
). On next boot, the
/sbin/iptables-restore
command is executed (this takes the saved
file as stdin). The /sbin/iptables-save
command dumps a thorough
listing of the iptables rules suitable for reconstruction. This output
can be scooped up and applied to other machines with the same firewall
requirements.
Simple Blocking
Sometimes something is happening on your network in real time that you’d just like to shut down. For example, let’s say that you want to block a naughty server from naughty children. Here’s the minimal intervention which I’ve had success with.
ss | grep -v 'https\|ssh' # Looking for a game server.
sudo iptables -A INPUT -s 69.197.42.49 -j DROP # Block it.
Professor Killian’s Rate Limiting Iptables Rules
So this is my [Chip’s] basic setup:
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 --source 137.0.0.0/8 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -m limit --limit 20/h --limit-burst 5 -j ACCEPT
-A INPUT -i lo -j ACCEPT
So I rate-limit the new ssh connections to 20/hour, with a burst of 5. To make sure I can get in during an attack, I whitelist some addresses. Packets on established connections are accepted by the first rule.
Other ssh brute force blocking ideas
Limit incoming SSH attempts to a low number. In my case I limit to 2 connections in 60 seconds. I can tighten it even more but this did a lot to kill brute force attempts.
iptables -I INPUT -p tcp -i vlan1 --dport 2242 -j DROP
iptables -I INPUT -p tcp -i vlan1 --dport 2242 -m state --state NEW -m limit --limit 2/min -j ACCEPT
iptables -I INPUT -p tcp -i vlan1 --dport 2242 -m state --state RELATED,ESTABLISHED -j ACCEPT
Also note that in general DROP may be a liability causing problems with legitimate use while not seriously deterring attackers. This excellent discussion of the merits of DROP vs. REJECT is quite interesting.
It may be interesting to explore TARPIT instead of DROP. Though that seems to weigh heavy on resources and prompts traffic to retry more than you might like.
Here’s a newer proposed method using rate limiting. Not sure about it.
sudo iptables -I INPUT -s 192.168.0.0/16 -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --set
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent \
--update --seconds 60 --hitcount 3 -j DROP
sudo apt-get install iptables-persistent
sudo netfilter-persistent save
Xed’s Firewall Clearing Script
This script basically takes out any firewall rules and resets network
access to be completely unrestricted. One use of this is to put this
in a cron job every so often (or an at
job) so that while you’re
working on sketchy rule changes, you don’t lock yourself out.
#!/bin/bash #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Written by Chris X Edwards 11.07.28 # Script to clear firewall rules. Use this in a cron job to ensure you # don't lock yourself out while working on rule changes. #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= VERBOSE=1 IPTABLES="/sbin/iptables" # iptables path and command function rule_setup { message $FUNCNAME # Try global flush/delete to clear anything already here. $IPTABLES --table filter --flush # Try to get any $IPTABLES --table filter --delete-chain # Try to get any user-def'd # flush any existing rules in built-in chains $IPTABLES --table filter --flush INPUT $IPTABLES --table filter --flush OUTPUT # set default policies $IPTABLES --policy INPUT ACCEPT $IPTABLES --policy OUTPUT ACCEPT $IPTABLES --policy FORWARD ACCEPT return } # end function rule_setup function message { if [ $VERBOSE ]; then echo " + Xed Custom Firewall: Executing $1"; fi } # end function message rule_setup
Here’s a smoother way to recover from bold firewall rule changes that are made remotely. Used with the previous code, it should reset things if you lose your connection.
#!/bin/bash SCRIPT=$$ function check4user { WAITFOR=60 # seconds function timeout { sleep $WAITFOR echo echo "You seem locked out. Resetting the firewall rules now." clearall kill $SCRIPT } # End function timeout. timeout & TIMEOUTID=$! read -p "Input something to indicate you are not locked out. " disown $TIMEOUTID kill $TIMEOUTID } # End function check4user. echo "Executing reckless firewall rule changes now." echo "This could lock you out!" check4user echo "Everything must have gone ok."
And of course if that is all too complicated, just clear the firewall in 10 minutes in case things don’t work out.
echo /see_script_above/firewall_reset | at now + 10min
sleep 600 && /see_script_above/firewall_reset # In screen/tmux.
Deleting A Firewall Rule
To get rid of a rule that is mistakenly added, you can run the same
command that added the rule but change the -I
to -D
. This is
useful if you just inserted a rule that, in immediate retrospect,
doesn’t make sense.
Added:
iptables -I OUTPUT -m state --state NEW -p tcp -d 10.0.0.111 --dport 636 -j ACCEPT
Ooops:
iptables -D OUTPUT -m state --state NEW -p tcp -d 10.0.0.111 --dport 636 -j ACCEPT
Now it’s gone.
A tougher problem is if there is a rule there that you didn’t put there and you need to weed it out. In this case, it’s often easiest to just delete it by it’s rule number. That brings up the next question of how do you know the rule number. Do this:
iptables -vnL --line-numbers
Then delete the offending rule with something like this:
iptables -D INPUT 1
Careful to get the right rule in the right chain because if you don’t, you can lock yourself out. Ahem…
Xed’s Simple Firewall Rules For Hosts
Here is a template for a simple set of rules that can be deployed to do the normal things done by iptables on a single machine. This includes blocking certain services, rate limiting connection attempts, logging, etc.
#!/bin/bash #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Simple Single Machine Firewall # Written by Chris X Edwards 11.07.28 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= #==========================DEFINITIONS========================== IPTABLES="echo /sbin/iptables" # iptables path and command # ---- DNS DNS1="172.19.0.252" # ns.example.edu DNS2="172.19.1.51" # ns0.example.edu DNS3="172.19.16.2" # ns1.example.edu DNS="${DNS1} ${DNS2} ${DNS3}" # ---- FRIENDS # SP Offices VLAN89="172.19.138.160/27 172.19.138.160/27" # SC Office VLAN79="172.19.243.96/27" # Machine Room VLAN 325 VLAN325="172.19.63.60 172.19.63.61 172.19.63.62 172.19.63.63 172.19.63.64" FRIENDS="${VLAN890} ${VLAN796} ${VLAN3252}" function rule_setup { message $FUNCNAME # Try global flush/delete to clear anything already here. $IPTABLES --table filter --flush # Try to get any $IPTABLES --table filter --delete-chain # Try to get any user-def'd # flush any existing rules in built-in chains $IPTABLES --table filter --flush INPUT $IPTABLES --table filter --flush OUTPUT # set default policies $IPTABLES --policy INPUT DROP $IPTABLES --policy OUTPUT DROP return } # end function rule_setup # This controls packets sent to and from firewall itself. function core_rules { # Rules that are a good idea in most cases. message $FUNCNAME # Always allow loopback $IPTABLES -A INPUT --in-interface lo -j ACCEPT $IPTABLES -A OUTPUT --out-interface lo -j ACCEPT # Allow all firewall pings for the moment $IPTABLES -A INPUT --protocol icmp -j ACCEPT $IPTABLES -A OUTPUT --protocol icmp -j ACCEPT # Allow machine to get portage cache with rsync $IPTABLES -A OUTPUT --protocol tcp --dport 873 -j ACCEPT $IPTABLES -A INPUT --protocol tcp ! --syn --sport 873 -j ACCEPT # Allow machine to get and receive DNS for D in DNS do $IPTABLES -A INPUT --protocol tcp ! --syn --src $D --sport domain -j ACCEPT $IPTABLES -A OUTPUT --protocol tcp ! --syn --dst $D --dport domain -j ACCEPT $IPTABLES -A INPUT --protocol udp --src $D --sport domain -j ACCEPT $IPTABLES -A OUTPUT --protocol udp --dst $D --dport domain -j ACCEPT done return } # end function core_rules
Xed’s Firewall Machine Template
Here’s a simple firewall script that will serve as a template for creating a dedicated firewall machine which can arbitrate connections for other machines. The nice thing about this format is that it can be scaled to complex applications in an organized way..
#!/bin/bash #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Simple Custom Firewall Server Rules # Written by Chris X Edwards 08.12.17 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= #==========================DEFINITIONS========================== VERBOSE=1 # Comment this out to hide debugging information IPTABLES="/sbin/iptables" # iptables path and command # ---- DNS DNS1="132.239.0.252" # ns0.ucsd.edu DNS2="128.54.16.2" # ns1.ucsd.edu # ---- FRIENDS XED="10.0.243.108" PRO="10.0.50.3 10.0.50.2 10.0.50.1" FRIENDS="${PRO} ${XED}" function rule_setup { message $FUNCNAME # Try global flush/delete to clear anything already here. $IPTABLES --table filter --flush # Try to get any $IPTABLES --table filter --delete-chain # Try to get any user-def'd # flush any existing rules in built-in chains $IPTABLES --table filter --flush INPUT $IPTABLES --table filter --flush OUTPUT # set default policies $IPTABLES --policy INPUT DROP $IPTABLES --policy OUTPUT DROP return } # end function rule_setup # This controls packets sent to and from firewall itself. function core_rules { # Rules that are a good idea in most cases. message $FUNCNAME # Always allow loopback $IPTABLES -A INPUT --in-interface lo -j ACCEPT $IPTABLES -A OUTPUT --out-interface lo -j ACCEPT # Allow all firewall pings for the moment $IPTABLES -A INPUT --protocol icmp -j ACCEPT $IPTABLES -A OUTPUT --protocol icmp -j ACCEPT # Allow machine to get portage cache with rsync $IPTABLES -A OUTPUT --protocol tcp --dport 873 -j ACCEPT $IPTABLES -A INPUT --protocol tcp ! --syn --sport 873 -j ACCEPT # Allow machine to get and receive DNS $IPTABLES -A INPUT --protocol tcp ! --syn --src $DNS1 --sport domain -j ACCEPT $IPTABLES -A INPUT --protocol tcp ! --syn --src $DNS2 --sport domain -j ACCEPT $IPTABLES -A INPUT --protocol udp --src $DNS1 --sport domain -j ACCEPT $IPTABLES -A INPUT --protocol udp --src $DNS2 --sport domain -j ACCEPT $IPTABLES -A OUTPUT --protocol tcp ! --syn --dst $DNS1 --dport domain -j ACCEPT $IPTABLES -A OUTPUT --protocol tcp ! --syn --dst $DNS2 --dport domain -j ACCEPT $IPTABLES -A OUTPUT --protocol udp --dst $DNS1 --dport domain -j ACCEPT $IPTABLES -A OUTPUT --protocol udp --dst $DNS2 --dport domain -j ACCEPT return } # end function core_rules function special_rules { # Rules that are specific to the mission of this host. message $FUNCNAME # Allow httpd proxy server to access the web ##$IPTABLES -A OUTPUT --protocol tcp --dport 80 -j ACCEPT ##$IPTABLES -A INPUT --protocol tcp ! --syn --sport 80 -j ACCEPT ##$IPTABLES -A OUTPUT --protocol tcp --dport 443 -j ACCEPT ##$IPTABLES -A INPUT --protocol tcp ! --syn --sport 443 -j ACCEPT #Allow a connection to Xed's personal machine for external testing. # Uncomment if other protocols besides ssh are needed. #$IPTABLES -A INPUT --protocol tcp --src $XED -j ACCEPT #$IPTABLES -A OUTPUT --protocol tcp --dst $XED -j ACCEPT #$IPTABLES -A INPUT --protocol udp --src $XED -j ACCEPT #$IPTABLES -A OUTPUT --protocol udp --dst $XED -j ACCEPT return } # end function special_rules function rules_for_friends { if [ $1 ]; then local MACHINE=$1; fi message "$FUNCNAME on $MACHINE" # Allow ssh connections from the machine $IPTABLES -A INPUT --protocol tcp --src ${MACHINE} --dport ssh -j ACCEPT $IPTABLES -A OUTPUT --protocol tcp --dst ${MACHINE} --sport ssh -j ACCEPT $IPTABLES -A INPUT --protocol udp --src ${MACHINE} --dport ssh -j ACCEPT $IPTABLES -A OUTPUT --protocol udp --dst ${MACHINE} --sport ssh -j ACCEPT # Allow this host to ssh to the machine $IPTABLES -A OUTPUT --protocol tcp --dst ${MACHINE} --dport ssh -j ACCEPT $IPTABLES -A INPUT --protocol tcp ! --syn --src ${MACHINE} --sport ssh -j ACCEPT # Allow httpd proxy server access from this machine $IPTABLES -A INPUT --protocol tcp --src ${MACHINE} --dport 8080 -j ACCEPT $IPTABLES -A OUTPUT --protocol tcp ! --syn --dst ${MACHINE} --sport 8080 -j ACCEPT return } # end rules_for_friends # This is a super simple test of basic firewall functionality. # This can be used to troubleshoot firewall problems and to assist # in initial configuration. Once everything proves that it works, # make sure this is disabled by making it false: TEST=false #TEST=true function simple_test_of_rules { message $FUNCNAME # For a simple test, reset the policies to be very permissive $IPTABLES --policy INPUT ACCEPT $IPTABLES --policy OUTPUT ACCEPT #$IPTABLES --policy FORWARD ACCEPT # Test the logging. Ping packets should be logged. #$IPTABLES -A FORWARD --protocol all -j LOG --log-prefix "| FORWARD |" #$IPTABLES -A INPUT --protocol all -j LOG --log-prefix "| I |" #$IPTABLES -A OUTPUT --protocol all -j LOG --log-prefix "| O |" # These test rules exclude SSH so you can test stuff over SSH without # filling up the logs with your own connection. $IPTABLES -A INPUT --protocol tcp --dport ! 22 -j LOG --log-prefix "| I |" $IPTABLES -A OUTPUT --protocol tcp --sport ! 22 -j LOG --log-prefix "| O |" $IPTABLES -A INPUT --protocol udp --dport ! 22 -j LOG --log-prefix "| I |" $IPTABLES -A OUTPUT --protocol udp --sport ! 22 -j LOG --log-prefix "| O |" return } # end function simple_test_of_rules function message { if [ $VERBOSE ]; then echo " + Xed Custom Firewall: Executing $1"; fi } # end function message #==========================MAIN SCRIPT========================== # Clean up built-in rules and set policy rule_setup # Provide a function to do basic testing of iptables functionality if $TEST ; then simple_test_of_rules; exit; fi # Establish rules for this host core_rules special_rules # Set up rules for each FRIEND who needs access for X in $FRIENDS do rules_for_friends $X; done
VLAN Identification
This isn’t really related to firewalls per se, but if you are managing VLANs it can be tricky to know what VLAN a live jack is on. Here is the technique I used to figure it out.
sudo tcpdump -nn -v -i eth0 -s 1500 -c 1 "ether[20:2] == 0x2000" | grep -i vlan