Fun with iptables, ulogd and wireshark

Having some fun tinkering with firewall rules today. I've been lazy and using Firestarter for a few years but it has a couple annoyances I didn't know how to work around and got to writing out my own iptables rules once again. I ended up with the relatively simple script below which does the following:

  • Enable NAT for the local network.
  • Forward some port traffic straight to internal systems.
  • Drop a few bizarre and most likely malicious spoofed source addresses and bad packet flags coming in on the external interface.
  • Accept some services locally. (just ssh for now)
  • Log and drop everything that isn't explicitly accepted. (traffic logged to /var/log/messages for convenient tail -f'ing)
  • Write a subset of those logged packets (the more interesting ones) to disk in their entirety using ulogd with the pcap plugin. (allowing you to crack open /var/log/ulogd/ulogd.pcap with wireshark and take a gander at what was actually in those packets)
  • Silently drop things that are uninteresting and get logged too much.

Packets are still logged to /var/log/messages which I like to keep an eye on with tail -f (though there's also a chain created for silently dropping things that are just too verbose). The script only sends a subset of these to ulogd for writing to disk and configuring what goes and what doesn't should be relatively straightforward.

Prerequisites, I think you just need to: "yum install -y ulogd ulogd-pcap wireshark-gnome", then modify /etc/ulogd.conf to enable the pcap plugin.

The firewall script itself:

#!/bin/sh

# Define external and internal network interfaces:
EXT_IF="eth1"
INT_IF="br0"

# Flush all current rules:
iptables -F
iptables -F INPUT
iptables -F OUTPUT
iptables -F FORWARD
iptables -F -t nat
iptables -F -t mangle
iptables -X

# Allow ssh early to avoid doing dumb things and locking ourselves out:
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Enable IP forwarding & NAT:
echo 1 > /proc/sys/net/ipv4/ip_forward

# Set default policies for INPUT, FORWARD and OUTPUT chains
iptables -P INPUT DROP
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT

# Enable NAT:
iptables -A POSTROUTING -t nat -o $EXT_IF -j MASQUERADE

# Redirect things to systems behind the firewall magically:
# Forward port 4096 to ssh on an internal system:
iptables -t nat -A PREROUTING -i $EXT_IF -p tcp --dport 4096 -j DNAT --to 192.168.0.40:22

# Always accept traffic to localhost and the internal interface:
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -i $INT_IF -j ACCEPT

# Create a log and drop chain:
iptables -N LOGNDROP
iptables -A LOGNDROP -m limit --limit 15/min -j LOG --log-prefix "DENIED: "
# Send interesting looking packets to ulog as well to write them to disk:
iptables -A LOGNDROP -p tcp --dport 1:9999 -j ULOG --ulog-nlgroup 1
iptables -A LOGNDROP -p tcp --sport 80 -j ULOG --ulog-nlgroup 1
iptables -A LOGNDROP -p tcp --sport 443 -j ULOG --ulog-nlgroup 1
# Last step, drop the packets:
iptables -A LOGNDROP -j DROP

# Create a chain for logging packets with bizarre flags:
iptables -N BADFLAGS
iptables -A BADFLAGS -m limit --limit 15/min -j LOG --log-prefix "BADFLAGS: "
# Send ALL of these to ulog:
iptables -A BADFLAGS -j ULOG --ulog-nlgroup 1
iptables -A BADFLAGS -j DROP

# Create a chain to drop some things without logging (too chatty):
iptables -N SILENTDROP
iptables -A SILENTDROP -j DROP

# Deny any packet coming in on the public internet interface
# which has a spoofed source address:
iptables -A INPUT -i $EXT_IF -s 192.168.0.0/16 -j BADFLAGS
iptables -A INPUT -i $EXT_IF -s 127.0.0.0/8 -j BADFLAGS
iptables -A INPUT -i $EXT_IF -s 10.0.0.0/8 -j BADFLAGS
# Deny bad flags:
iptables -A INPUT -p tcp --tcp-flags ALL FIN,URG,PSH -j BADFLAGS
iptables -A INPUT -p tcp --tcp-flags ALL ALL -j BADFLAGS
iptables -A INPUT -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j BADFLAGS
iptables -A INPUT -p tcp --tcp-flags ALL NONE -j BADFLAGS
iptables -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j BADFLAGS
iptables -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j BADFLAGS

# Allow services:
# NOTE: Already accepted ssh earlier.

# Custom bittorrent ports:
iptables -A INPUT -p tcp --dport 23881:23891 -j ACCEPT
iptables -A INPUT -p udp --dport 23881:23891 -j ACCEPT

# Allow legit traffic:
# DHCP
iptables -A INPUT -p udp --sport 67 --dport 68 -j ACCEPT

# Permit packets in that are part of existing and related connections:
iptables -A INPUT -i $EXT_IF -m state --state ESTABLISHED,RELATED -j ACCEPT

# Drop some things silently:
iptables -A INPUT -p udp --sport 137 --dport 137 -j SILENTDROP

# If we got this far, log and drop:
iptables -A INPUT -j LOGNDROP

# Save settings:
/sbin/service iptables save

Adjust as you see fit and checkout /var/log/ulogd/ulogd.pcap in wireshark to see what's actually in the more interesting packets.

I'd still like to get the logging a little more friendly to the eyes which I've read is possible with ulogd, will update post if I get that figured out.

In case it's of interest to anyone else, the box is also a KVM virt host with all guests configured to use the bridged internal interface br0. As far as I can tell this causes no issues and behaves just as you would expect if they were all physical systems on the internal network.

Now what to do with all those logged packets? I'll be damned if I remember the significance of half of what wireshark will tell me but it's cool none the less.

Comments

Interesting read and i

Interesting read and i realise it is 2012 however,

iptables -F clears all chains if you don't specify any therefore
iptables -F INPUT
iptables -F OUTPUT
iptables -F FORWARD

all do nothing that iptables -F hasn't done before them.

You're doing iptables -P INPUT DROP but at the end of your rules you have, iptables -A INPUT -j LOGNDROP so there is no point in having a default action of drop as it will never trigger.

Post new comment

The content of this field is kept private and will not be shown publicly.
Type the characters you see in this picture. (verify using audio)
Type the characters you see in the picture above; if you can't read them, submit the form and a new image will be generated. Not case sensitive.