Eine einfache stateful Firewall mit iptables

Mit iptables kann man unter Linux eine Firewall konfigurieren. In diesem Artikel zeige ich die wichtigsten Befehle und Arbeitsschritte um eine Firewall für IPv4 und IPv6 einzurichten.

Hilfsvariabeln und Hilfsfunktionen

Folgende Hilfsvariabeln vereinfachen einem die kontrolle über de einzelnen Befehle und Konfigurationsdateien:

IPTABLES="/sbin/iptables"
IPTABLES_SAVE="/sbin/iptables-save"
IPTABLESCONFIG="/etc/iptables/iptables.rules"
IP6TABLES="/sbin/ip6tables"
IP6TABLES_SAVE="/sbin/ip6tables-save"
IP6TABLESCONFIG="/etc/iptables/ip6tables.rules"
IP46TABLES="ip46tables"

Die Hilfsfunktion ip64tables verwende ich, um Befehle gleichzeitig mit iptables und ip6tables auszuführen. Somit verhindert man den Fehler, dass man Regeln nur für IPv4 und nicht für IPv6 erstellt:

ip46tables(){
  $IPTABLES $@
  $IP6TABLES $@
}

Firewall initialisieren

Zuerst werden alle bestehenden Regeln gelöscht:

$IP46TABLES -F INPUT
$IP46TABLES -F OUTPUT
$IP46TABLES -F FORWARD

Als restriktive Firewall sollen standarsmässig alle Pakete verworfen (Target DROP) werden:

$IP46TABLES -P INPUT DROP
$IP46TABLES -P OUTPUT DROP
$IP46TABLES -P FORWARD DROP

Der gesamte lokale Verkehr soll akzeptiert werden (z. B. für den X-Server oder lokale Sockets).

$IP46TABLES -A INPUT -i lo -j ACCEPT
$IP46TABLES -A OUTPUT -o lo -j ACCEPT

Stateful Firewall

Folgende zwei Regeln helfen einem eine stateful Firewall zu erstellen. Dabei werden mit dem Modul conntrack die Verbindungen beobachtet. Falls eine Verbindung zustande kommt, werden auch alle weiteren Pakete dieser Verbindung zugelassen.

$IP46TABLES -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -m comment --comment "Stateful_INPUT"
$IP46TABLES -A OUTPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -m comment --comment "Stateful_OUTPUT"

Verbindungen nach Aussen zulassen

Für jeden Service, den man nutzen will, erstellt man eine Regel, welche den ausgehenden Verkehr auf dem gewünschten Port zulässt. Hier am Beispiel whois, welches den Port 43 über TCP verwendet. Damit die stateful Regeln funktionieren, verwendet man das Modul conntrack um die Verbindung zu tracken. Die Antwortpakete auf die ausgehende Verbindung werden somit über die zuvor definierten Regeln der stateful Firewall zugelassen.

$IP46TABLES -A OUTPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT -m comment --comment "SSH"

Verbindungen nach Innen zulassen

Will man selber einen Service anbieten, muss man den gewünschten Port von aussen nach innen zulassen. Wichtig ist auch hier, dass man das Modul conntrack verwendet, damit die Antwortpakete auch gesendet werden können.

$IP46TABLES -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT -m comment --comment "Accept_SSH_IN"

Spezielle IPv4 Konfiguration

Diese Regel lässt eingehende ICMP Echo Requests (ping) zu. Dies Regel gilt nur für IPv4, da IPv6 die Option icmpv6 verwendet. Dabei ist zu beachten, dass jetzt $IPTABLES statt $IP46TABLES verwendet wird.

$IPTABLES -A INPUT -p icmp --icmp-type echo-request -m state --state NEW -j ACCEPT -m comment --comment "ICMP_echo-request"

Spezielle IPv6 Konfiguration

Damit man sich eine IPv6 Adresse generieren kann, muss man zuerst den Präfix vom Router erhalten. Dies geschieht über die Router Advertisments und Router Solicitations.

$IP6TABLES -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT -m comment --comment "ICMPv6_router-advertisement"
$IP6TABLES -A INPUT -p icmpv6 --icmpv6-type router-solicitation -j ACCEPT -m comment --comment "ICMPv6_router-solicitation"
$IP6TABLES -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT -m comment --comment "ICMPv6_echo-request"
$IP6TABLES -A INPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT -m comment --comment "ICMPv6_echo-reply"

Die Namensauflösung der IPv6 Adressen in MAC-Adressen geschieht über die Neighbor Discovery mittels Neighbor Solicitations und Neighbor Advertisements.

$IP6TABLES -A INPUT -p icmpv6 --icmpv6-type neighbour-advertisement -j ACCEPT -m comment --comment "ICMPv6_neighbour-advertisement"
$IP6TABLES -A INPUT -p icmpv6 --icmpv6-type neighbour-solicitation -j ACCEPT -m comment --comment "ICMPv6_neighbour-solicitation"

Kernelmodule

Damit FTP funktioniert, muss das Kernelmodul nf_conntrack_ftp geladen sein. Das kann man manuell mit modprobe machen:

$ sudo modprobe nf_conntrack_ftp

Oder mit der Datei /etc/modules-load.d/iptables.conf:

$ cat /etc/modules-load.d/iptables.conf
nf_conntrack_ftp

Firewall Skript

Das ganze kann man jetzt schön in ein Skript packen, damit man iptables gut kontrollieren kann. Ich verwende diese Firewall unter Arch Linux. Mein Firewall-Skript sieht folgendermassen aus:

#!/bin/bash
#
# firewall - iptables script
# Emanuel Duss
#

IPTABLESCONFIG="/etc/iptables/iptables.rules"
IP6TABLESCONFIG="/etc/iptables/ip6tables.rules"
IPTABLES="/sbin/iptables"
IP6TABLES="/sbin/ip6tables"
IPTABLES_SAVE="/sbin/iptables-save"
IP6TABLES_SAVE="/sbin/ip6tables-save"
MODPROBE="/sbin/modprobe"
SYSCTL="/sbin/sysctl"

help(){
cat << EOI
Usage:
  firewall (start|stop|status|save|help)
EOI

}

start(){
  echo "[+] Starting firewall... "

  $IPTABLES -F INPUT
  $IP6TABLES -F INPUT
  $IPTABLES -F OUTPUT
  $IP6TABLES -F OUTPUT
  $IPTABLES -F FORWARD
  $IP6TABLES -F FORWARD

  $IPTABLES -P INPUT DROP
  $IP6TABLES -P INPUT DROP
  $IPTABLES -P OUTPUT DROP
  $IP6TABLES -P OUTPUT DROP
  $IPTABLES -P FORWARD DROP
  $IP6TABLES -P FORWARD DROP

  $IPTABLES -A INPUT -i lo -j ACCEPT
  $IP6TABLES -A INPUT -i lo -j ACCEPT
  $IPTABLES -A OUTPUT -o lo -j ACCEPT
  $IP6TABLES -A OUTPUT -o lo -j ACCEPT

  output_chain
  input_chain

  echo "[+] Firewall started"
}

stop(){
  echo -n "[+] Stopping firewall... "

  $IPTABLES -F INPUT
  $IP6TABLES -F INPUT
  $IPTABLES -F OUTPUT
  $IP6TABLES -F OUTPUT
  $IPTABLES -F FORWARD
  $IP6TABLES -F FORWARD

  $IPTABLES -P INPUT ACCEPT
  $IP6TABLES -P INPUT ACCEPT
  $IPTABLES -P OUTPUT ACCEPT
  $IP6TABLES -P OUTPUT ACCEPT
  $IPTABLES -P FORWARD ACCEPT
  $IP6TABLES -P FORWARD ACCEPT

  echo "Done."
}

status(){
  echo "[+] iptables:"
  $IPTABLES --version
  $IPTABLES -vnL --line-numbers
  echo "[+] ip6tables:"
  $IP6TABLES --version
  $IP6TABLES -vnL --line-numbers
}

save(){
  echo -n "[+] Saving iptables rules... "
  $IPTABLES_SAVE > "$IPTABLESCONFIG"
  echo "Done. (`wc -l $IPTABLESCONFIG | cut -d' ' -f1` Lines saved.)"
  echo -n "[+] Saving ip6tables rules... "
  $IP6TABLES_SAVE > "$IP6TABLESCONFIG"
  echo "Done. (`wc -l $IP6TABLESCONFIG | cut -d' ' -f1` Lines saved.)"
}

input_chain(){
  echo -n "[+] Applying input rules... "

  # Stateful
  $IPTABLES -A INPUT -m conntrack --ctstate INVALID -j LOG --log-prefix "DROP INVALID " --log-ip-options --log-tcp-options
  $IPTABLES -A INPUT -m conntrack --ctstate INVALID -j DROP
  $IPTABLES -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

  # Accept
  $IPTABLES -A INPUT -p icmp --icmp-type echo-request -m state --state NEW -j ACCEPT
  $IPTABLES -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j LOG --log-prefix "ALLOW SSH IN "
  $IPTABLES -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT # SSH
  $IPTABLES -A INPUT -p tcp --dport 6600 -m conntrack --ctstate NEW -m mac --mac-source 5C:0A:5B:93:3B:CB -j LOG --log-prefix "ALLOW MPD IN "
  $IPTABLES -A INPUT -p tcp --dport 6600 -m conntrack --ctstate NEW -m mac --mac-source 5C:0A:5B:93:3B:CB -j ACCEPT # MPD Telefon

  # Drop
  $IPTABLES -A INPUT -p tcp --dport 23 -m conntrack --ctstate NEW -j LOG --log-prefix "DROP Telnet IN" # Telnet
  $IPTABLES -A INPUT -p tcp --dport 23 -m conntrack --ctstate NEW -j DROP
  $IPTABLES -A INPUT -m addrtype --dst-type BROADCAST -j DROP

  # Log
  $IPTABLES -A INPUT ! -i lo -j LOG --log-prefix "DROP IN " --log-ip-options --log-tcp-options

  echo  "Done."
}

output_chain(){
  echo -n "[+] Applying output rules... "

  # Stateful
  $IPTABLES -A OUTPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

  # Accept
  $IPTABLES -A OUTPUT -p icmp --icmp-type echo-request -m conntrack --ctstate NEW -j ACCEPT # ICMP echo-request
  $IPTABLES -I OUTPUT -p udp --dport 67:68 -m conntrack --ctstate NEW -j ACCEPT # DHCP
  $IPTABLES -A OUTPUT -p udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT # DNS
  $IP6TABLES -A OUTPUT -p udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT
  $IPTABLES -A OUTPUT -p tcp --dport 53 -m conntrack --ctstate NEW -j ACCEPT
  $IP6TABLES -A OUTPUT -p tcp --dport 53 -m conntrack --ctstate NEW -j ACCEPT
  $IPTABLES -A OUTPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT # HTTP
  $IP6TABLES -A OUTPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
  $IPTABLES -A OUTPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT # HTTPS
  $IP6TABLES -A OUTPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT
  $IPTABLES -A OUTPUT -p tcp --dport 21 -m conntrack --ctstate NEW -j ACCEPT # FTP (nf_conntrack_ftp muss geladen sein)
  $IPTABLES -A OUTPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT # SSH
  $IP6TABLES -A OUTPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
  $IPTABLES -A OUTPUT -p tcp --dport 43 -m conntrack --ctstate NEW -j ACCEPT # Whois
  $IPTABLES -A OUTPUT -p udp --dport 123 -m conntrack --ctstate NEW -j ACCEPT # NTP
  $IPTABLES -A OUTPUT -p tcp --dport 5222 -m conntrack --ctstate NEW -j ACCEPT # XMPP
  $IPTABLES -A OUTPUT -p udp --dport 5222 -m conntrack --ctstate NEW -j ACCEPT
  $IPTABLES -A OUTPUT -p tcp --dport 6667 -m conntrack --ctstate NEW -j ACCEPT # IRC
  $IPTABLES -A OUTPUT -p tcp --dport 6697 -m conntrack --ctstate NEW -j ACCEPT
  $IPTABLES -A OUTPUT -p tcp --dport 465 -m conntrack --ctstate NEW -j ACCEPT # SMTP Gmail
  $IPTABLES -A OUTPUT -p tcp --dport 993 -m conntrack --ctstate NEW -j ACCEPT # IMAPS
  $IPTABLES -A OUTPUT -p udp --dport 1194 -m conntrack --ctstate NEW -j ACCEPT # OpenVPN
  $IPTABLES -A OUTPUT -p tcp --dport 9418 -m conntrack --ctstate NEW -j ACCEPT # GIT
  $IPTABLES -A OUTPUT -p udp --dport 9418 -m conntrack --ctstate NEW -j ACCEPT
  $IPTABLES -A OUTPUT -p udp --dport 137 -m conntrack --ctstate NEW -j ACCEPT # Samba; NETBIOS Name Service
  $IPTABLES -A OUTPUT -p udp --dport 138 -m conntrack --ctstate NEW -j ACCEPT # Samba; NETBIOS Datagram Service
  $IPTABLES -A OUTPUT -p tcp --dport 139 -m conntrack --ctstate NEW -j ACCEPT # Samba; NETBIOS Session Service
  $IPTABLES -A OUTPUT -p tcp --dport 445 -m conntrack --ctstate NEW -j ACCEPT # Samba; Microsoft-DS
  $IPTABLES -A OUTPUT -p tcp --dport 6969 -m conntrack --ctstate NEW -j ACCEPT # Bittorrent; Kommunikation mit Tracker
  $IPTABLES -A OUTPUT -p tcp --dport 6881:6889 -m conntrack --ctstate NEW -j ACCEPT # Bittorrent; Kommunikation mit anderen Clients (Download/Upload von Files)

  echo  "Done."
}

if [ "$UID" -ne "0" ]
then
  echo "[!] Only root can run this script."
  exit 1
fi

case "$1"
in
  "start") start ;;
  "stop") stop ;;
  "restart") stop; start ;;
  "status") status ;;
  "save") save ;;
  "help") help ;;
  *) help; exit 1 ;;
esac

Firewall kontrollieren

So startet man die Firewall:

$ sudo ./firewall start
[+] Starting firewall...
[+] Applying output rules... Done.
[+] Applying input rules... Done.
[+] Firewall started

Und wieder stoppen:

$ sudo firewall stop
[+] Stopping firewall... Done.

Firewall als Service aktivieren

Nach einem Reboot sind die Regeln wieder weg. Deshalb kann man den iptables Service aktivieren und gleich starten:

$ sudo systemctl enable iptables
$ sudo systemctl start iptables

Regeln speichern

Dieser Service liest die iptables Regeln von der Datei /etc/iptables/iptables.rules bzw. /etc/iptables/ip6tables.rules. Damit diese Dateien die iptables Regeln erhalten, speichert man diese so:

$ sudo ./firewall save
[+] Saving iptables rules... Done. (55 Lines saved.)
[+] Saving ip6tables rules... Done. (14 Lines saved.)

Regeln anschauen

Die Regeln kann man in diesen Dateien oder direkt mit dem firewall Script anschauen:

$ sudo ./firewall status
[+] iptables:
iptables v1.4.20
Chain INPUT (policy DROP 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination        
1        0     0 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0          
2        3   120 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate INVALID LOG flags 6 level 4 prefix "DROP INVALID "
3        3   120 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate INVALID
4       69  9510 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
5        0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0            icmptype 8 state NEW
6        0     0 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 ctstate NEW LOG flags 0 level 4 prefix "ALLOW SSH IN "
7        0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 ctstate NEW
8        0     0 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:6600 ctstate NEW MAC 5C:0A:5B:93:3B:CB LOG flags 0 level 4 prefix "ALLOW MPD IN "
9        0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:6600 ctstate NEW MAC 5C:0A:5B:93:3B:CB
10       0     0 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:23 ctstate NEW LOG flags 0 level 4 prefix "DROP Telnet IN"
11       0     0 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:23 ctstate NEW
12       2   144 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type BROADCAST
13       0     0 LOG        all  --  !lo    *       0.0.0.0/0            0.0.0.0/0            LOG flags 6 level 4 prefix "DROP IN "
14       0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp flags:0x17/0x02 limit: avg 1/sec burst 5
15       0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0            icmptype 8 limit: avg 1/sec burst 5
16       0     0 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp flags:!0x17/0x02 state NEW
17       0     0 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp flags:0x3F/0x3F
18       0     0 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp flags:0x3F/0x00
19       0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 state NEW recent: SET name: SSH side: source mask: 255.255.255.255
20       0     0 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 recent: UPDATE seconds: 60 hit_count: 4 TTL-Match name: SSH side: source mask: 255.255.255.255 LOG flags 0 level 4 prefix "SSH_brute_force "
21       0     0 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 recent: UPDATE seconds: 60 hit_count: 4 TTL-Match name: SSH side: source mask: 255.255.255.255

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

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination        
1        0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpts:67:68 ctstate NEW
2        0     0 ACCEPT     all  --  *      lo      0.0.0.0/0            0.0.0.0/0          
3       77 27355 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
4        0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0            icmptype 8 ctstate NEW
5        0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:53 ctstate NEW
6        0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:53 ctstate NEW
7        2   120 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80 ctstate NEW
8        2   120 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:443 ctstate NEW
9        0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:21 ctstate NEW
10       0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 ctstate NEW
11       0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:43 ctstate NEW
12       0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:123 ctstate NEW
13       0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:5222 ctstate NEW
14       0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:5222 ctstate NEW
15       0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:6667 ctstate NEW
16       0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:6697 ctstate NEW
17       0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:465 ctstate NEW
18       0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:993 ctstate NEW
19       0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:1194 ctstate NEW
20       0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:9418 ctstate NEW
21       0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:9418 ctstate NEW
22       0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:137 ctstate NEW
23       0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:138 ctstate NEW
24       0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:139 ctstate NEW
25       1    60 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:445 ctstate NEW
26       0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:6969 ctstate NEW
27       0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpts:6881:6889 ctstate NEW

Log betrachten

Die Logs kann man normal mit journalctl verfolgen:

$ sudo journalctl -f

Download

Die Firewall gibt es auch auf GitHub in meinem Scripts Repository. Die Firewall wurde noch etwas erweitert. Link: firewall.

Links und weitere Informationen

Leave a Comment