Firewall selbst entwickeln
Inhalt

    1. Vorwort
    2. Einleitung
    3. Der Paketfilter von Kernel 2.4
    4. Die Konstruktion
        4.1. Grundsätzliches
        4.2. Der Rahmen
        4.3. ipfilter-util
             4.3.1. Stop und Close
             4.3.2. Start
             4.3.3. CreateBucket
             4.3.4. EgressFilter
             4.3.5. AcceptLocal
             4.3.6. InOutTCP und InOutUDP
             4.3.7. InTCP
             4.3.8. OutTCP
             4.3.9. InUDP
             4.3.10. OutUDP
             4.3.11. OutEqualUDP
             4.3.12. OutFTP
    5. Installation
    6. Einschränkungen
    7. Quellen

Autor:
hjb

1. Vorwort

Es gibt zahlreiche sogenannte Firewall-Skripte und Frontends für Linux. Doch ihrer praktischen Verwendung stehen oft mehrere Gründe im Weg:

Besonders der zweite Punkt ist entscheidend. Wenn ich einen Firewall einrichte, will ich 100% sicher sein, daß er so funktioniert, wie ich mir das vorstelle. Wie aber will ich das bei einem vorgefertigten Programm bewerkstelligen? Trial and Error wäre hier angesagt. Doch bevor ich mehrere Programme daraufhin untersucht habe, ob sie korrekt funktionieren, habe ich schneller etwas Eigenes geschrieben.

Daher stelle ich hier eine Art Baukasten für einen Paketfilter vor, der folgende Bedingungen erfüllt:

Ich gehe nicht davon aus, daß das hier vorgestellte Skript der Weisheit letzter Schluß ist. Vielmehr werden sich vermutlich noch einige Änderungen und Erkenntnisse ergeben, die in eine neuere Version dieses Artikels einfließen werden. Die Hauptsache ist zunächst einmal, daß das Skript tut, was ich mir als Ziel gesetzt habe.

Dieser Artikel ist kein Tutorial zur Wirkungsweise von Netfilter oder dem iptables-Programm. Entsprechende Dokumentation, teilweise auch in Deutsch, gibt es bereits (s. Quellen). Es wäre unsinnig, dies zu wiederholen. Stattdessen liefert dieser Artikel ein Beispiel aus der Praxis und hilft damit hoffentlich, die Lücke zwischen Dokumentation und realer Anwendung kleiner zu machen.

2. Einleitung

Allzu oft wird Firewall mit Paketfilter gleichgesetzt. Das ist falsch. Paketfilter sind nur ein Teil einer Firewall-Konfiguration. Ich will hier aber trotzdem nur auf Paketfilter eingehen, und zwar auf die Paketfilter, die Bestandteil des Linux-Kernels sind. Es gibt auch eine oder mehrere Implementierungen außerhalb des Kernels, doch habe ich noch keine näheren Informationen darüber.

Definieren wir schnell einmal ein paar Begriffe. Der Firewall-Rechner ist der Rechner, der die Paketfilter implementiert. Es kann ein einzelner Rechner mit Internet-Zugang (Modem, ISDN, DSL oder gar LAN) sein, es kann aber auch ein Router sein, an den ein oder mehrere (bei mir letzteres) lokale Netze angeschlossen sind. Das "Draußen" ist demzufolge das Internet und wird über das Netzwerk-Interface erreicht, das dorthin führt. Bei Modem-Zugang also ppp0, bei ISDN ippp0, und bei anderen Zugängen eth0, eth1 oder was auch immer.

Ein Paketfilter ist zwar wichtig, garantiert für sich allein aber noch keine absolute Sicherheit. Würde man alle Pakete nach draußen sperren, hätte man absolute Sicherheit erreicht, doch aus pragmatischen Gründen wird man das eine oder andere Paket durch den Filter durchlassen wollen. Dies könnte ein Angreifer jedoch ausnutzen, um mit gefälschten Paketen eventuell Zugang zu dem Rechner zu erlangen. Je mehr Löcher man im Paketfilter lassen muß, desto größer die Gefahr. Filter-Kriterien sind die IP-Adressen von Sender und Empfänger, die Portnummer von Sender und Empfänger, das Protokoll, die Schnittstelle, die hereinkommende und ausgehende Pakete nehmen, und die Richtung (ins Internet oder vom Internet kommend). Wir müssen davon ausgehen, daß die Adresse und die Portnummer eines hereinkommenden Paketes gefälscht sein können. Ferner müssen wir alle Pakete ablehnen, die nicht für uns bestimmt sind. Das mag trivial erscheinen, weil man glauben könnte, daß Pakete, die nicht für uns bestimmt sind, erst gar nicht bei uns ankommen. Doch dem ist nicht so.

3. Der Paketfilter von Kernel 2.4

Kernel 2.4 kommt mit einer neuen Implementierung der Paketfilter, der vierten oder fünften in der Geschichte von Linux. Der Name der Implementierung ist Netfilter. Um diese zu benutzen, bedarf es auch eines neuen Tools, iptables. Version 1.2 wird mindestens vorausgesetzt.

Zwar erlaubt Netfilter es mit Hilfe von Modulen, Rückwärtskompatibilität mit Linux 2.2 und sogar 2.0 herzustellen, doch ich rate davon ab. Erstens funktioniert dann Masquerading nicht, was für viele Nutzer wichtig ist, und zweitens bringt es nichts, auf einem Auslaufmodell herumzureiten. Kernel 2.4 ist hier und wird spätestens mit Version 2.4.3 weite Verbreitung erfahren. Netfilter ist endlich die Implementierung der Paketfilter, die die optimale Flexibilität und Erweiterbarkeit garantiert und dazu noch exzellente Performance bietet. Man darf davon ausgehen, daß sie mehr Bestand haben wird als ihre Vorgänger.

Es ist wichtig, die grundlegenden Konzepte von Netfilter zu kennen. Hier eine Kurzfassung. Netfilter arbeitet mit "Ketten" von Regeln. Ketten sind wie Unterprogramme. Regeln können in eine neue Kette verzweigen, und Ketten können zur aufrufenden Kette zurückkehren. Dieses Konzept stammt von den ipchains von Linux 2.2.

Es gibt drei vordefinierte Ketten: INPUT, OUTPUT und FORWARD. Ferner kann man beliebige weitere Ketten definieren. Standardmäßig sind nach dem Booten alle Ketten leer, und alle Pakete werden durchgelassen. Im Unterschied zu früher durchläuft jedes eingehende oder ausgehende Netzwerk-Paket nur noch eine einzige Kette:

Ein Paket durchläuft eine Kette, indem alle Regeln in der Kette der Reihe nach abgearbeitet werden, bis entweder eine Regel entscheidet, was mit dem Paket passiert, oder alle Regeln durchlaufen sind, worauf die Default Policy angewandt wird. Das Schicksal des Pakets kann im Wesentlichen nur zwei mögliche Verläufe nehmen: Es wird ignoriert (DROP) oder normal verarbeitet (ACCEPT). Der Weg bis zu dieser Entscheidung kann natürlich beliebig komplex werden, je nach Umfang der Anforderungen. Darüber hinaus kann auf diesem Weg noch einiges mit dem Paket passieren. Wir beschränken uns hier aber auf das Logging.

4. Die Konstruktion

4.1. Grundsätzliches

Das von mir entwickelte Skript behandelt kein Masquerading (auch Network Address Translation, NAT, genannt). Das kann man in einem separaten Skript behandeln, das nicht Thema dieses Artikels sein soll.

Das Skript sollte aufgerufen werden, bevor die entsprechenden Netzwerk-Schnittstellen definiert sind. Dadurch entsteht kein Loch im Filter, auch wenn dieses nur Sekundenbruchteile lang sein sollte.

Sicherheit läßt sich nur erreichen, wenn der Paketfilter möglichst restriktiv ist. Das heißt, es muß alles ausgefiltert werden, was nicht explizit erlaubt ist. Die umgekehrte Vorgehensweise, nämlich alles zu erlauben, was nicht explizit verboten ist, bietet keinerlei Schutz vor unvorhergesehenen Attacken.

Alle Verstöße gegen die Filterregeln werden protokolliert (sie erscheinen im Kernel-Log). Das ist neben dem Entdecken von Angriffen auch für das Debuggen nützlich.

4.2. Der Rahmen

Das Skript ist wegen der Übersichtlichkeit und späteren Erweiterbarkeit auf zwei Dateien verteilt. Das "Hauptprogramm" des Skripts ist für das Starten und Stoppen der IP-Filter zuständig:

#!/bin/sh

. "`dirname $0`/ipfilter-util"

case "$1" in
  start)
    echo -n "Starting IP filtering... "
    Start
    echo "done."
    ;;
  stop)
    echo -n "Stopping IP filtering... "
    Stop
    echo "done."
    ;;
  close)
    echo -n "Closing IP interfaces... "
    Close
    echo "done."
    ;;
  *)
    echo "Usage: $0 start|stop|close"
    exit 1
esac

exit 0

Ich habe den Rahmen so einfach gehalten, daß er mit jeder Distribution funktioniert. Jede Distribution kommt jedoch mit einem Beispiel eines Init-Skripts, das man stattdessen auch verwenden könnte. Dazu muß man den obigen Code in das Beispiel-Skript einfügen. Rudimentäre Kenntnisse in der Shell-Programmierung sollte man dazu aber schon haben.

4.3. ipfilter-util

4.3.1 Stop und Close

Wie man am obigen Skript sieht, muß das Skript ipfilter-util nur drei Funktionen zur Verfügung stellen: Start, Stop und Close. Es stellt sich heraus, daß die Funktionen Stop und Close sehr simpel sind:

Stop()
{
    Flush ACCEPT
}

Close()
{
    Flush DROP
}

Wir haben also eine weitere Funktion zu definieren:

Flush()
{
    ${IPTABLES} -P INPUT $1
    ${IPTABLES} -P OUTPUT $1
    ${IPTABLES} -P FORWARD $1
    ${IPTABLES} -F
    ${IPTABLES} -F INPUT
    ${IPTABLES} -F OUTPUT
    ${IPTABLES} -F FORWARD
    ${IPTABLES} -X destroy     > /dev/null 2>&1
    ${IPTABLES} -X in_private  > /dev/null 2>&1
    ${IPTABLES} -X out_private > /dev/null 2>&1
}

Flush benötigt ein Argument, das entweder ACCEPT (Aufruf von Stop) oder DROP (Aufruf von Close) ist. Dies bestimmt die vorgegebene Policy des Paketfilters: Ist sie ACCEPT, werden alle Pakete durchgelassen. Ist sie DROP, werden alle Pakete vernichtet. Letzteres hat weitgehend dieselbe Wirkung wie das Herausreißen des Netzwerkkabels und ist nur für Notfälle gedacht.

Ferner entfernt Flush alle Regeln aus den vordefinierten Ketten (Option -F) und löscht die selbstdefinierten Ketten (Option -X). Wie diese definiert werden, werden wir später noch sehen. Die Ausgabe der letzten Kommandos geht nach /dev/null, damit wir keine Fehlermeldungen sehen, falls die Ketten nicht existieren sollten.

Zu erwähnen ist noch die Variable IPTABLES. Diese enthält den Pfad des iptables-Programms, in meinem System /sbin/iptables. Wo wir gerade dabei sind, ist jetzt eigentlich eine gute Gelegenheit, die drei Variablen zu beschreiben, die am Anfang des Skripts definiert sind. Diese müssen Sie an Ihre eigene Umgebung anpassen.

INTERFACE="ippp0"
LOCAL="192.168.0.0/24"
IPTABLES="/sbin/iptables"

INTERFACE ist der Name des Netzwerk-Interfaces, das zum Internet führt. Für die Leute, die eine dynamische IP-Adresse haben, und das ist ja die Mehrheit, ist es nicht sinnvoll, die IP in den Regeln zu verwenden. Stattdessen nimmt man den Namen des Interfaces. LOCAL ist der Adreßbereich des lokalen Netzes, das meist über das Interface eth0 erreicht wird. IPTABLES schließlich ist der Pfad des iptables-Programms, wie schon gesagt.

4.3.2 Start

Wir kommen nun zur Funktion Start, die unsere Filter definiert. Im Wesentlichen besteht Start aus einer Reihe von weiteren Funktionsaufrufen. Diese Funktionen werde ich im Folgenden vorstellen. Die ersten Funktionen initialisieren den Filter und definieren einige Grundregeln. Der Mittelteil definiert, welche Pakete durch den Filter passieren dürfen, und sollte von Ihnen an Ihre Bedürfnisse angepaßt werden. Er besteht aus den Funktionen InOutTCP, OutTCP, OutFTP, OutEqualUDP und InOutUDP. Danach folgen Regeln für ICMP (ich lasse derzeit alles durch). Zum Schluß folgen Regeln, die alle Pakete, die es bis hierher geschafft haben, vernichten. Im Grunde benötigt man diese Regeln nicht, aber sie sorgen dafür, daß weggeworfene Pakete im Syslog erscheinen. Ohne spezielle Regel würden sie einfach verschwinden. Das wäre weniger gut, weil man ja wissen will, wenn jemand einen Crack-Versuch unternommen hat.

Start()
{
    Close
    CreateBucket
    EgressFilter
    AcceptLocal

    InOutTCP  22                 # auth, Telnet and ssh
    OutTCP    http 81 https 8080
    OutTCP    ftp smtp finger pop3 nntp
    OutTCP    871                # sup
    OutTCP    realplayer
    OutTCP    cvspserver
    OutTCP    6667               # IRC
    OutFTP

    OutEqualUDP ntp

    InOutUDP domain

    # Firewall ICMP. ICMP is useful so allow any by default.
    ${IPTABLES} -A INPUT   -j ACCEPT -p icmp
    ${IPTABLES} -A OUTPUT  -j ACCEPT -p icmp
    ${IPTABLES} -A FORWARD -j ACCEPT -p icmp

    ${IPTABLES} -A INPUT  -j destroy
    ${IPTABLES} -A OUTPUT -j destroy
}

Die erste Funktion, Close, kennen wir bereits. Sie dient hier zum Rücksetzen der Ketten auf einen definierten Zustand. Außerdem wird die Default Policy gleich mal richtig gesetzt, nämlich auf DROP.

4.3.3 CreateBucket

CreateBucket()
{
    ${IPTABLES} -N destroy

    # These rules are only there for logging.
    ${IPTABLES} -A destroy -j LOG --log-level notice -m limit
    ${IPTABLES} -A destroy -j DROP
}

CreateBucket ist eine simple Funktion, die eine neue Kette destroy erzeugt und dieser zwei Regeln hinzufügt. Jedes Paket, das in dieser Kette landet, wird vernichtet, jedoch vorher im Syslog protokolliert. Das ist auch der einzige Sinn dieser Kette, nämlich sicherzustellen, daß kein unerlaubtes Paket unbemerkt bleibt. CreateBucket erzeugt also gewissermaßen unseren (Abfall-)Eimer.

4.3.4 EgressFilter

EgressFilter()
{
    # Dont't let private addresses in.
    ${IPTABLES} -N in_private
    ${IPTABLES} -F in_private
    ${IPTABLES} -A in_private  -j destroy   -s 127.0.0.0/8
    ${IPTABLES} -A in_private  -j destroy   -s 10.0.0.0/8
    ${IPTABLES} -A in_private  -j destroy   -s 172.16.0.0/12
    ${IPTABLES} -A in_private  -j destroy   -s 192.168.0.0/16
    ${IPTABLES} -A in_private  -j destroy   -s 224.0.0.0/4
    ${IPTABLES} -A in_private  -j destroy   -s 240.0.0.0/4

    # Dont't let private addresses escape.
    ${IPTABLES} -N out_private
    ${IPTABLES} -F out_private
    ${IPTABLES} -A out_private -j destroy   -d 127.0.0.0/8
    ${IPTABLES} -A out_private -j destroy   -d 10.0.0.0/8
    ${IPTABLES} -A out_private -j destroy   -d 172.16.0.0/12
    ${IPTABLES} -A out_private -j destroy   -d 192.168.0.0/16
    ${IPTABLES} -A out_private -j destroy   -d 224.0.0.0/4
    ${IPTABLES} -A out_private -j destroy   -d 240.0.0.0/4

    ${IPTABLES} -A INPUT       -j in_private  -i ${INTERFACE}
    ${IPTABLES} -A FORWARD     -j in_private  -i ${INTERFACE}
    ${IPTABLES} -A OUTPUT      -j out_private -o ${INTERFACE}
    ${IPTABLES} -A FORWARD     -j out_private -o ${INTERFACE}
}

Bei Egressfilter handelt es sich genaugenommen um einen kombinierten Egress- und Ingress-Filter. Ersterer verhindert, daß Pakete mit privaten IP-Adressen (RFC 1537) nach draußen gelangen. Letzterer verhindert, daß Pakete mit solchen Absenderadressen von draußen zu uns herein gelangen, denn da solche Adressen im Internet nicht erlaubt sind, muß es sich um Fälschungen handeln.

Die Funktion definiert zwei neue Chains, in_private und out_private. Die vier Regeln am Ende der Funktion sorgen dafür, daß alle Pakete eine der beiden Chains durchlaufen. Das Sprungziel für Pakete, die ausgefiltert werden, ist unsere Chain destroy, die wir in der Funktion CreateBucket definiert haben.

4.3.5 AcceptLocal

AcceptLocal()
{
    ${IPTABLES} -A INPUT  -j ACCEPT -i \! ${INTERFACE}
    ${IPTABLES} -A OUTPUT -j ACCEPT -o \! ${INTERFACE}
    ${IPTABLES} -A FORWARD -j ACCEPT -i \! ${INTERFACE} \
            -o \! ${INTERFACE}
    ${IPTABLES} -A FORWARD -j destroy -i ${INTERFACE} \
            -o ${INTERFACE}
}

AcceptLocal sorgt dafür, daß der Datenverkehr über unseren Router problemlos vonstatten geht, solange Quelle und Ziel im internen LAN liegen. Diese Funktion ist nur nötig, wenn man mehr als eine interne Netzwerkschnittstelle hat. Man kann sich natürlich darüber streiten, ob es sinnvoll ist, auf dem Internet-Gateway diese Routerfunktion zu haben. Bei mir ist es im Moment eben so, aber ich habe vor, das zu ändern.

Als zusätzlichen Bonus (letzte Regel) vernichtet die Funktion alle Pakete, die aus dem Internet stammen und gleich wieder ins Internet geroutet werden wollen. Solche Pakete sind höchst verdächtig, da sie unseren Rechner als Router mißbrauchen.

4.3.6 InOutTCP und InOutUDP

Bis jetzt haben wir einen Rahmen geschaffen, der alle Pakete zwischen LAN und Internet sperrt und alles andere weitgehend erlaubt. Nun müssen wir daran gehen, einige Protokolle zwischen dem LAN und dem Internet freizugeben. Dies tun wir getrennt für Verbindungen, die von innen nach außen und umgekehrt gehen. Verbindungen von außen nach innen werden wir weitgehend gesperrt lassen, denn dafür besteht auf einem Heim-Rechner kaum ein Anlaß. Es sei denn, man betreibt einen Server, was bei den heutigen Internet-Zugängen mit dynamischen IPs aus mehreren Gründen problematisch ist.

Es folgen zunächst zwei Funktionen, mit denen man Protokolle in beiden Richtungen freigeben kann. Sie delegieren die ganze Arbeit an einfachere Funktionen, die wir gleich vorstellen werden. Alle diese Funktionen erwarten als Argumente beliebig viele Portnummern oder Protokollnamen. Wenn man Namen verwendet, müssen diese in der Datei /etc/services einer Portnummer zugewiesen sein. Beispiele sind oben in der Funktion Start zu finden.

InOutTCP()
{
    InTCP $*
    OutTCP $*
}

InOutUDP()
{
    InUDP $*
    OutUDP $*
}

4.3.7 InTCP

InTCP()
{
    for i in $*
    do
        ${IPTABLES} -A INPUT  -j ACCEPT -i ${INTERFACE} -p tcp \
                --dport $i -m state --state NEW,ESTABLISHED,RELATED
        ${IPTABLES} -A OUTPUT -j ACCEPT -o ${INTERFACE} -p tcp \
                --sport $i -m state --state ESTABLISHED,RELATED
        ${IPTABLES} -A FORWARD  -j ACCEPT -i ${INTERFACE} -p tcp \
                --dport $i -m state --state NEW,ESTABLISHED,RELATED
        ${IPTABLES} -A FORWARD -j ACCEPT -o ${INTERFACE} -p tcp \
                --sport $i -m state --state ESTABLISHED,RELATED
    done
}

Diese Funktion erlaubt hereinkommende Verbindungen auf einem oder mehreren Ports. Sie ist nicht zuständig für ausgehende Verbindungen. Zu einer ankommenden Verbindung gehören Pakete in beide Richtungen, die jedoch unterschiedlich behandelt werden. Daher benötigt man eine Regel für jede Richtung. Wenn man wie hier auch noch Pakete hat, die entweder für den lokalen Rechner bestimmt sind oder weitergeleitet werden, dann ergibt das vier Regeln insgesamt. Auf einem normalen Heimrechner, der nicht mit weiteren Rechnern im LAN verbunden ist, kann man die Regeln für die FORWARD-Chain weglassen. Umgekehrt könnte bzw. sollte man auf einem reinen Router, der keinerlei lokale Dienste zur Verfügung stellt, die ersten beiden Regeln weglassen.

Die Regeln machen Gebrauch von "Stateful Filtering", was bedeutet, daß sich das System den Zustand von Verbindungen merkt. Damit können alle Pakete, die den aktuellen Zustand der Verbindung verletzen oder gar nicht zu einer bestehenden Verbindung gehören können, ausgefiltert werden. Das Einschalten des Stateful Filtering geschieht mit der Option -m state. Mit der Option --state wird ausgewählt, in welchen Verbindungszuständen ein Paket passieren darf. Da wir hier ankommende Verbindungen freischalten wollen, erlauben wir den Zustand NEW für ankommende Pakete aus dem Internet. Durch die Option -i ${INTERFACE} wird sichergestellt, daß nur aus dem Internet kommende Pakete betroffen sind. Zusätzlich geben wir für beide Richtungen die Zustände ESTABLISHED und RELATED frei. ESTABLISHED steht für eine aufgebaute Verbindung, auf der ganz normal Pakete ausgetauscht werden. RELATED steht für Pakete, die mit der Verbindung in Zusammenhang stehen, besonders ICMP-Pakete.

4.3.8 OutTCP

OutTCP()
{
    for i in $*
    do
        ${IPTABLES} -A OUTPUT -j ACCEPT -o ${INTERFACE} -p tcp \
                --sport 1024: --dport $i -m state --state NEW,ESTABLISHED,RELATED
        ${IPTABLES} -A INPUT  -j ACCEPT -i ${INTERFACE} -p tcp \
                --sport $i -m state --state ESTABLISHED,RELATED
        ${IPTABLES} -A FORWARD -j ACCEPT -o ${INTERFACE} -p tcp \
                -s ${LOCAL} --sport 1024: --dport $i -m state --state NEW,ESTABLISHED,RELATED
        ${IPTABLES} -A FORWARD  -j ACCEPT -i ${INTERFACE} -p tcp \
                --sport $i -d ${LOCAL} -m state --state ESTABLISHED,RELATED
    done
}

Diese Funktion entspricht InTCP, nur werden hiermit Verbinundgen nach außen freigegeben, also das, was man in 99% aller Fälle benötigt. Also sind die Richtungen gegenüber InTCP gerade vertauscht. Als zusätzliche Restriktion kommt noch hinzu, daß die Portnummer des Absenders über 1023 liegen muß. Dies ist OK bei allen TCP-basierten Protokollen, die ich verwende, doch es kann Ausnahmen geben. FTP ist solch eine Ausnahme, doch auch noch aus anderen Gründen.

4.3.9 InUDP

InUDP()
{
    for i in $*
    do
        ${IPTABLES} -A INPUT  -j ACCEPT -i ${INTERFACE} -p udp \
                --dport $i -m state --state NEW,ESTABLISHED,RELATED
        ${IPTABLES} -A OUTPUT -j ACCEPT -o ${INTERFACE} -p udp \
                --sport $i --dport 1024: -m state --state ESTABLISHED,RELATED
        ${IPTABLES} -A FORWARD  -j ACCEPT -i ${INTERFACE} -p udp \
                --dport $i -m state --state NEW,ESTABLISHED,RELATED
        ${IPTABLES} -A FORWARD -j ACCEPT -o ${INTERFACE} -p udp \
                --sport $i -m state --state ESTABLISHED,RELATED
    done
}

Diese Funktion entspricht InTCP, nur ist sie für das UDP-Protokoll. Der einzige Unterschied ist in der Tat der Protokollname, und das, obwohl UDP kein verbindungsorientiertes Protokoll ist.

4.3.10 OutUDP

OutUDP()
{
    for i in $*
    do
        ${IPTABLES} -A OUTPUT -j ACCEPT -o ${INTERFACE} -p udp \
                --sport 1024: --dport $i -m state --state NEW,ESTABLISHED,RELATED
        ${IPTABLES} -A INPUT  -j ACCEPT -i ${INTERFACE} -p udp \
                --sport $i -m state --state ESTABLISHED,RELATED
        ${IPTABLES} -A FORWARD -j ACCEPT -o ${INTERFACE} -p udp \
                -s ${LOCAL} --sport 1024: --dport $i -m state --state NEW,ESTABLISHED,RELATED
        ${IPTABLES} -A FORWARD  -j ACCEPT -i ${INTERFACE} -p udp \
                --sport $i -d ${LOCAL} -m state --state ESTABLISHED,RELATED
    done
}

Diese Funktion entspricht OutTCP, nur ist sie für das UDP-Protokoll. Der einzige Unterschied ist in der Tat der Protokollname, und das, obwohl UDP kein verbindungsorientiertes Protokoll ist.

4.3.11 OutEqualUDP

OutEqualUDP()
{
    for i in $*
    do
        ${IPTABLES} -A OUTPUT -j ACCEPT -o ${INTERFACE} -p udp \
                --sport $i --dport $i -m state --state NEW,ESTABLISHED,RELATED
        ${IPTABLES} -A INPUT  -j ACCEPT -i ${INTERFACE} -p udp \
                --sport $i --dport $i -m state --state ESTABLISHED,RELATED
    done
}

Diese Funktion läßt ausgehende Verbindungen für UDP-basierte Protokolle zu, die nicht unserer ursprünglichen Annahme folgen, daß die Portnummer des Senders über 1023 liegt. Das einzige mir bekannte Beispiel ist NTP, das Network Time Protocol. Bei dessen Implementierung sind die Portnummern von Sender und Empfänger identisch. Hier habe ich auf die FORWARD-Regeln verzichtet, d.h. weitergeleitet wird nicht. Die entsprechenden Regeln bei Bedarf hinzuzufügen, sei dem Leser als Übung überlassen.

4.3.12 OutFTP

OutFTP()
{
    InOutTCP ftp-data
    ${IPTABLES} -A INPUT  -j ACCEPT -i ${INTERFACE} -p tcp \
            --sport ftp-data -m state --state NEW,ESTABLISHED,RELATED
}

Für aktives FTP ist die Funktion OutFTP gedacht. Aktives FTP ist bekanntlich ein Spezialfall, der bei einem Firewall einige Kopfschmerzen bereitet, weshalb oft passives FTP oder ein FTP-Proxy verwendet wird. Passives FTP habe ich hier nicht vorgesehen, doch mit dem Stateful Filtering sollte es kein Problem sein, es hinzuzufügen.

Die Funktion erlaubt zunächst Verbindungen in beide Richtungen auf dem ftp-data Port, welcher als Port Nr. 20 definiert ist. Durch die nachfolgende Regeln werden Pakete auf einem beliebigen Port akzeptiert, wenn die Portnummer des Senders 20 ist. Ohne Stateful Filtering würde dies sozusagen ein riesiges Scheunentor öffnen.

5. Installation

Zur Installation kopiert man die beiden Skripte ipfilter und ipfilter-util ins Verzeichnis der Init-Skripte. Dieses ist distributions-abhängig, oft jedoch /etc/rc.d. ipfilter muß ausführbar sein. Beide Skripte sollten root gehören. Danach sollte man die Skripte erst einmal durch Aufruf von /etc/rc.d/ipfilter start und /etc/rc.d/ipfilter stop testen. Insbesondere sollte man testen, ob alle benötigten Applikationen noch funktionieren. Dann sollte man testen, ob unerwünschte Pakete tatsächlich ausgefiltert werden. Dies funktioniert offensichtlich nur von einem Rechner im Internet, doch gibt es Webseiten, die das als Dienst anbieten.

Netfilter ist sehr modular aufgebaut. Eine ganze Reihe von Kernel-Modulen müssen geladen werden, damit alles funktioniert. Glücklicherweise sorgt das Programm iptables dafür, daß alle Module automatisch geladen werden.

Funktioniert alles zufriedenstellend, kann man dafür sorgen, daß das Skript automatisch beim Systemstart ausgeführt wird. Wie das genau geschieht, ist wiederum distributions-abhängig.

6. Einschränkungen

Das Skript unterstützt noch nicht mehr als ein lokales Netz. Entweder erweitert man in diesem Fall die Funktionen In, Out usw., was ja simpel genug ist, oder man faßt die IP-Adressen der Netze unter einer Adresse/Maske zusammen.

Mein Filter ist sehr restriktiv. Jedes neue Protokoll muß extra freigegeben werden. Dies hat den Vorteil der absoluten Kontrolle, kann aber auch unpraktisch sein. Im Prinzip könnte man alle Protokolle in der Richtung von LAN ins Internet freigeben. Solange die Gegenrichtung gesperrt bleibt, kann dennoch kein Eindringling ins LAN. Wer die Notwendigkeit dafür sieht, kann das machen. Doch mein Ziel war maximale Sicherheit, nicht maximaler Komfort.

7. Quellen

Linux 2.4 Paket Filtering HOWTO
Deutsche Übersetzung des Linux 2.4 Paket Filtering HOWTO
iptables(8) - Manual Page für iptables Version 1.2

Feedback ist wichtig für die Verbesserung des Service
Copyright (C) hjb
Datum: 2001-03-11 - Pro-Linux  (Info)