Paketfilter, eine Einführung


Inhaltsverzeichnis

  1. Syntax der verschiedenen Paketfilterloesungen
    1. Access Control Lists fuer Ciscos
    2. Access Control Lists fuer Linux mit ipchains
    3. Access Control Lists fuer Linux mit ipfwadm
    4. Access Control Lists fuer Ascends
  2. Grundsaetzliche Probleme
    1. Fragmente
    2. FTP
    3. DNS
    4. ICMP (Internet Control Message Protocol)(
    5. RPC-Dienste
  3. Designprinzip
  4. Praktisches Beispiel
    1. Anforderungen
    2. die ACL
    3. Einbindung ins System
  5. Fazit

Eine kleine Einfuehrung in Paketfilter.

Sinn dieses Textes ist ungefaehr zu zeigen wie man es macht und worauf es ankommt. In den Beispielen wird die Syntax der Linux-ipchains verwendet, aber die Regeln lassen sich ohne weiteres auf andere Paketfiltersysteme uebertragen.

Ganz wichtig: Dieser Text ist nicht fehlerfrei. Und damit meine ich keine Rechtschreibfehler. Ich uebernehme keine Gewaehr.


Syntax der verschiedenen Paketfilterloesungen

Access Control Lists für Ciscos:

Beim cisco löscht man eine ACL mit "no ip access-list NNN", wobei NNN die Nummer der ACL ist. Ansonsten gibt es nur noch ein Kommando zum Anhängen.
Wichtig: NNN bestimmt den Typ der ACL - ich verwende hier "extended ACLs", die bessere Steuerung als die standard ACLs ermoeglichen, die fuer heutige Zwecke nicht mehr ausreichen.
NNN bewegt sich bei Extended ACLs im Bereich 101..199.

Wichtig ist: jede neue Kommando wird hinten an die ACL angehängt. im Zweifelsfalls heißt also die Lösung "wegwerfen und neu machen", oder rcp nehmen. Wobei Wegwerfen, wenn man die Änderung remote macht, riskant ist (hint: nicht vorhandene ACLs, die irgendwo noch angesprochen werden, wirken wie "DENY alles"). rcp funktioniert ganz gut.
Im Zweifelsfall legt man eine neue ACL an, trägt die anstelle der Alten in den Interfaces ein, und löscht dann die alte ACL.

Die Syntax ist:

    access-list [permit|deny] NNN PROTOCOL SOURCE [PORTSPEC] \
    DEST [PORTSPEC] [established] [log|log-input]
Darin bedeutet:
permit:
paket darf durch
deny:
paket wird verworfen
PROTOCOL:
Art des Protokolls:
SOURCE:
erlaubte Sourcen
PORTSPEC:
Source/Zielportangabe. Sourceport direkt nach Sourceadresse, Zielport direkt nach Zieladresse,
DEST:
siehe SOURCE
established:
Nur für TCP gültig, matcht auf alle Pakete ohne SYN-Flag (SYN ist Verbindungseröffnung).
log:
syslog-Meldung für matchende Pakete erzeugen
log-input:
syslog-Meldung für matchende Pakete erzeugen, mit Angabe des Interfaces (erst ab 11.2).
ACLs muessen, damit sie wirksam sind, noch einem Interface zugeordnet werden. Beispiel:
interface bri0
ip access-group NNN in|out
(in oder out aus Sicht des Ciscos).

Access Control Lists für Linux mit ipchains

ipchains ist der ACL-Mechanismus, der ab Linux-2.1.10x zur Verfügung steht. Mir gefällt es aber deutlich besser als die alte Lösung, und die funktioniert sowieso nicht mehr, weil ich 2.1.102 verwende, also ...

ipchains ist ein Kommando zur Verwaltung der ACLs im Kernel. Im Normalfall hängt es eine Zeile an, oder löscht eine Zeile oder fügt eine Zeile ein. Anders gesagt - an dieser Stelle ist es flexibler als cisco-IOS. Ich benutze das aber so nicht, da ich es vorziehe immer mit ganzen Listen zu hantieren und nicht einzelne Kommandos auszuprobieren (aber das ist nur meine Arbeitsweise, nicht prinzipiell besser).

Es gibt drei globale ACL - input, output und forward.
I/o dürfte klar sein, forward bestimmt das Weiterleiten von Paketen (I+O zusammen leisten nicht das was F leistet, und F leistet nichts von dem was I oder O leisten).
In diese globalen ACLs kann man noch weitere Einhängen.
Die Defaultpolicy stellt man mit "ipchains -P" ein, für jede ACL extra. Beispiel siehe unten.

Außer ACCEPT (paket ist OK) gibt es noch DENY (laß es verschwinden) und REJECT (sende Host unreachable). DENY ist ein guter Default für input Rules.

Bei meiner Vorgehensweise - Configdatei verändern und dann ACL aktivieren, gibt es ein Problem - wenn ich die ACL zwischen- zeitlich löschen würde um sie neu aufzubauen hätte ich einen Moment lang keine. Deshalb habe ich ein Script um die eigentliche ACL gemacht, es setzt die Defaults, legt eine temporäre ACL an, macht sie zur aktiven ACL, loescht die Alte, legt die Alte wieder an, macht sie aktiv, löscht die Temporäre wieder.

Vorteil bei diese Vorgehensweise: der Name ist die allermeiste Zeit identisch - das hilft bei Scripten die die ACL auswerten, ungemein.

Syntax:

ipchains -A NAME REGEL
eine Regel an die ACL NAME anhängen
ipchains -D NAME REGEL
eine Regel löschen. Exakter Name bitte
ipchains -D NAME NUMMER
Regel Nummer NUMMER löschen
ipchains -C NAME REGEL
"was wäre wenn". See man page.
ipchains -R MAME NUMMER REGEL
eine Regel ersetzen
ipchains -I NAME NUMMER REGEL
an Position NUMMER einfügen. Der alte Eintrag und alle folgenden rutschen nach hinten.
ipchains -P NAME POLICY
Defaultpolicy der ACL NAME setzen.
Legende:
NAME
Name einer ACL/Chain (bis 8 Zeichen)
NUMMER
1..n.
POLICY
REGEL
der Inhalt der Regel, der Code der bestimmt welche Pakete auf diese Regel passen und was mit ihnen geschieht.
Regeln werden mit Optionen des Kommandos ipchains angelegt. Das ist eindeutig nicht so schön wie beim Cisco.
Einige Bedingungen lassen sich negieren, dazu stellt man ein Ausrufezeichen voran.
-p [!]PROTOCOL
Angabe des Protokolls, auf das die Regel paßt. Moeglich sind
-s [!]ADDRESS [![PORTSPEC]]
Angabe der Absenderadresse
-d [!]]ADDRESS [![PORTSPEC]]
Angabe der Zieladresse. Siehe Sourceadresse.
[!] -y
SYN-Flag gesetzt (! -y: SYN-Flag nicht gesetzt == "established")
-l
logging (syslog) einschalten
-i XXX
Regel gilt für Interface XXX.
-j NAME
jump to ACL NAME.
Fuer NAME erlaubt sind die Namen anderer ACLs (Rekursion sollte hier vermieden werden - nur um das mal gesagt zu haben), oder die Namen der Policies (DENY,ACCEPT,REJECT).
[!] -f
passt auf das 2. bis letzte Teil eines fragmentierten Paketes.

Bei ICMP-Paketen ist der Sourceport der ICMP-Typ, und der Destination-Port die genauere Spezifizierung ("unreachable" + "host unreachable").

Access Control Lists für Linux mit ipfwadm

ipfwadm ist die "alte" Paketfilterlösung für Linux. Sie kann Fragmente nicht vernünftig handhaben (was allerdings auch auf den cisco-Code zutrifft) und kann nicht atomar die Regeln Updaten.

Nichtsdestotrotz ist 2.0.33 natürlich um etliches stabiler als 2.1.102.

Die Syntax ist recht ähnlich der des ipchains-Kommandos, nur ein paar Optionen heissen anders, und accept,deny,reject wollen kleingeschrieben werden.
Es wird grundsätzlich an die Liste angehängt (von denen es auch hier drei gibt, allerdings ohne die Möglichkeit auf andere ACLs zu verweisen.

Ich spare mir hier die detaillierte Auffuehrung der Optionen. Die Syntax ist hinreichend aehnlich der der ipchains, allerdings muss man auf gross/kleinschreibung achten. Ein Beispiel:

    ipfwadm -I    -a accept -b -P udp -S 0.0.0.0/0 53 \
                                      -D 194.245.80.2/32 53

Access Control Lists für Ascends:

Besprochen wird nur die Konfiguration von Filterlisten über Radius. Die Onlinekonfiguration ist einfach zu häßlich und unübersichtlich.
Die Syntax ist:
  Ascend-Data-Filter="ip [in|out] [forward|drop] \
    [dstip DESTIP/MASKBITS] [srcip SRCIP/MASKBITS] \
    [PROTO [dstport CMP DESTPORT] \
           [srcport CMP SRCPORT] [EST]]"
Kurze Legende:
ip:
Schlüsselwort. Muß einfach sein.
in:
Eingehende Pakete filtern (Gegenseite sendet zum Ascend).
out:
Ausgehende Pakete filtern (Ascend sendet zur Gegenseite).
forward:
Auf die Regel passende Pakete weiterleiten.
drop:
Auf die Regel passende Pakete verwerfen.
destip:
Schlüsselwort
DESTIP/MASKBITS
Ziel-IP-Adresse: Netzmaske und Maskenbits (194.245.80.0/28).
srcip:
Schlüsselwort
SRCIP/MASKBITS
Quell-IP-Adresse: Netzmaske und Maskenbits (194.245.80.0/28).
PROTO:
Protokoll. Aus der Dokumentation - so man die so nennen kann - geht nicht hervor ob man auch andere Protokolle anhand ihrer Nummern angeben kann. Ich vermute mal es geht.
dstport:
Schlüsselwort. Dieses und die folgenden Parameter koennen nur fuer TCP und UDP Pakete genutzt werden.
CMP:
<,>, = oder !=. Dient zum Vergleich der Portnummern.
DESTPORT:
Spezifiziert den Zielport. Hier koennen Nummern oder auch einige Namen (ftp-data,ftp,telnet,smtp,domain,finger,www,nntp,ntp und noch ein paar mehr - wobei es bezeichnen ist daß der Ascend Radius Server www versteht und den offiziellen Namen http nicht). Im Zweifelsfall sollte man Portnummern verwenden.
Wenn mal jemand Lust hat sollte er dem Radiusserver getservicebyname beibringen. Es ist mir unklar warum die Ports darin hardcoded sind (na gut, das ist echter Ascend-Code).
srcport:
Schlüsselwort. Dieses und die folgenden Parameter koennen nur fuer TCP und UDP Pakete genutzt werden.
SRCPORT:
Spezifiziert den Quellport. Siehe oben.
est:
Wirkung wie established beim Cisco. Es kann für TCP-Verbindungen und laut Ascend-Doku auch für UDP-Verbindungen genutzt werden (wobei letzteres recht unwahrscheinlich ist, es ergibt einfach keinen Sinn).
Die Reihenfolge der Schlüsselwörter ist ziemlich variabel, etwa so flexibel wie man es erwarten würde.
Neben diesen Data Filtern gibt es noch sog. Call Filter. Die Syntax ist identisch, nur statt Ascend-Data-Filter ist Ascend-Call-Filter zu benutzen. Sie bestimmen ob der Ascend wegen eines Paket eine Verbindung aufbaut und setzen gegebenenfalls den Timeoutzähler zurück, beeinflussen aber nicht den Transfer der Pakete über schon bestehende Verbindungen.
Wenn kein Eintrag der Filterliste paßt verwirft der Ascend das Paket bei Data Filtern, und ignoriert es bei Call Filtern.

Will man ICMP oder andere Protokolle filtern, die keine Ports kennen, wird es umstaendlicher - dann muß man zu sog. generischen Filtern greifen, die schwieriger zu konfigurieren sind und mit denen man einzelne Bits im IP-Paket untersuchen kann.

Plus und Minus


Grundsätzliche Probleme

Fragmente

Manchmal sind IP-Pakete zu groß für das Übertragungsmedium. Sie werden dann (und manchmal auch aus anderen Gründen) gesplittet.
Die Steuerinformation - welche Hosts, welche Ports - kommt dabei nur im ersten Paket (oder über X Pakete verteilt, wenn man die Pakete klein genug splittet. Das ist beinahe immer nur bei Attacken zu beobachten).
Wie geht man beim Paketfiltern damit geschickt um? Antwort: Gar nicht. Es gibt nichts was ein einfacher Paketfilter machen kann - er kann nicht erkennen ob das Fragment nicht doch ungefährlich und notwendig ist.
Der Linuxkernel bietet aber die Moeglichkeit, alle Pakete, auch die die nur durchgereicht werden, vor dem Verarbeiten zusammenzusetzen. Dazu muss man
  1. CONFIG_IP_ALWAYS_DEFRAG=y in /usr/src/linux/.config eintragen,
  2. make oldconfig && make install modules modules_install aufrufen (oder wie auch immer macht es sonst macht. lilo nicht vergessen!).
Nachteile:

Was tun wenn man nur einen Cisco hat? You lose, dafuer ist der Router ungeeignet.


FTP

FTP überträgt die Daten und Directory Listings auf einer anderen Verbindung als die Kommandos. Und da gibt es zwei Versionen:
active mode
der Server öffnet eine FTP-Verbindung zum client. Typischerweise will "man" (der client) das aber nicht - schliesslich ist schwer unterscheidbar ob der Connect von Port 20 (ftp-data) auf Port 2049 (ahem - Network Failure System) nicht doch eine Attacke ist.
passive mode
der Client öffnet den zweiten Kanal selbst, auf einen Port, den der Server vorher mitgeteilt hat (aus Sicht des Paketfilters auf irgendeinen Port von irgendeinem Port!).
Klingt gut? Wär's auch, wenn nicht:

Wie entkommt man diesem Dilemma, wenn man selbst FTP-Server sein will und selbst FTP nutzen koennen will?
# ftp ipchains -A $i -p tcp -y \ -d 194.245.80.2 21 -j ACCEPT # ftp-data -> NFS ipchains -A $i -p tcp -y -s 0/0 20 \ -d 194.245.80.2 2049 -j REJECT # ftp-data -> X ipchains -A $i -p tcp -y -s 0/0 20 \ -d 194.245.80.2 6000:6010 -j REJECT # ftp-data -> andere Port >1024: OK. ipchains -A $i -p tcp -y -s 0/0 20 \ -d 194.245.80.2 1024: -j ACCEPT

Man erlaubt also den Zugriff auf gewisse Ports von port 20 aus - das kann zwar ein FTP-Server sitzen, _aber_ das muss nicht. Das kann auch der böse Einbrecher sein. In jedem Fall sollte man andere kritische Ports auch sperren - siehe unten unter RPC.
Prinzipiell ist das nicht wesentlich sicherer als diese Zeile, die nebenbei auch incoming passive mode ftp erlaubt:

ipchains -A $i -p tcp -y -s 0/0 1024: \
         -d 194.245.80.2 1024:    -j ACCEPT 
Sie macht es nur einfacher zu sehen dass da noch andere Sachen sind ...

Fazit: ftp ist ein Problem, daß sich mit den Mitteln eines Paketfilters nicht lösen läßt. man müßte hier schon mehr Möglichkeiten haben - aber das setzt Überwachung der FTP-Control-Connection voraus und ähnelt damit mehr einem richtigen Firewall.
Bei manchen FTP-Servern kann man die Fähigkeit zum passive mode abschalten, die mir bekannten Clients gehen dann automatisch zum active mode über (falls der Firewall auf der Seite des Clients das zuläßt. Katze, Schwanz, Biß).
In jedem Fall sollte man tunlichst darauf achten daß man, wenn man den Zugriff von Port 20 auf Port X (X>1023) oder von überall auf Port X freigibt, alle Port noch absichert die oberhalb von 1023 liegen und in Benutzung sind, z.B. X, NFS, Datenbankapplikationen. Leider ist das nicht einfach, siehe unten unter RPC (dasselbe kann aber auch fuer ganz andere Dienste gelten, nicht nur fuer RPC).


DNS

auch bei DNS lauert eine Überraschung. Und zwar koennen ueberlange DNS-Antworten TCP verlangen. Wenn eine Antwort zu lang ist für das UDP-Paket (aus Gründen die ich nicht kenne sind UDP-DNS-Pakete auf 5xx Bytes limitiert) muß der Anfragende den Rest per TCP abholen.

Das heißt: wenn man DNS-Server ist muß man auch beliebige TCP-Verbindungen von aussen auf port 53 erlauben _oder_ dafür Sorge tragen daß der Fall nicht auftreten kann (keine überlangen TXT-Pakete).


ICMP (Internet Control Message Protocol)

ICMP beinhaltet ping, ping reply, host unreachable, source quench (hey Absender, Du sendest zu schnell, halt mal ein), etc usw.
Man sieht also problemlos daß dies zu filtern üble Konsequenzen haben kann.

Andererseits sind icmps beliebte Träger für Attacken (jolt).

Was also tun? Hat man eine Linuxmachine zur Verfuegung kann man diese alle Pakete defragmentieren lassen. Ansonsten kann man versuchen nur die wichtigsten ICMPs durchzulassen - dazu zaehlt insbesondere Typ 3, die "unreachables".


RPC-Dienste

Meine Meinung über nfs (Network Failure System), portmap und Co duerfte ja hinreichend bekannt sein. Block them.
Andere RPC-Dienste bind()en sich beim Start auf einen festen Port, sagen wir mal 1024++ (oder, wenn man sie später stoppt und neu starten, auch auf einen ganz anderen). Und da nehmen sie fleissig Verbindungen an.
Dabei helfen keine TCP-Wrapper, es gibt keine hilfreichen Syslogeintraege.

Normalerweise funktioniert RPC so, daß man den Portmapper fragt "hey du, auf welchem Port ist lockd?". Wenn Portmapper geblockt ist kommt halt keine Antwort, aber das macht nichts, mit strobe oder nmap findet man den Port ja trotzdem - man weiß zwar nicht _was_ das ist, aber offen ist er dennoch - was mir nicht schmeckt, da SUNs RPC-Code eine eher unsichere Angelegenheit ist.

Eine Moeglichkeit waere, diesen Muell nicht zu starten. Dummerweise gibt es Systeme die RPC-Dienste von sich aus starten wenn andere Dienste gestartet werden (so startet der Linux-knfsd auch lockd und rpciod).
Die Andere ist, das Zeug absolut gar nicht zu starten. Also auch nfsd nicht. Das ist natuerlich manchmal unpraktisch.
Die Letzte und Beste ist, Verbindungen von aussen nur auf bestimmte Ports zu erlauben - was allerdings der Tod fuer passive-mode-FTP-Zugriff auf den FTP-Server hinter dem Firewall ist. Im Zweifelsfall ist das die sinnvollste Alternative.


Designprinzip

Sicherheit, die nachhaltig anhaelt, erreicht man nur wenn man explizit erlaubt was erlaubt sein soll. Man erreicht sie nicht wenn man nur verbietet was bekannt gefaehrlich ist.

Gruende dafuer:

  1. siehe oben unter RPC. Man kann nicht wissen was das System hinter dem eigenen Ruecken tut.
  2. Konfigurationsfehler, z.B. ein irrtuemlich geloeschtes # in der inetd.cnf, haben weniger schlimme bis keine Folgen.
  3. Ohne grossen Aufwand bleibt niemand up-to-date bei den Problemen die die diversen eingesetzten Systeme haben.

Praktisches Beispiel

Für die folgenden Anforderungen soll ein Firewall designed werden:

Anforderungen