Wenn eine Applikation über das Netz kommuniziert, funktioniert sie natürlich nur dann, wenn das Netz zwischen den
beteiligten Maschinen diese Kommunikation auch durchleitet. Das ist im Internet üblicherweise der Fall; beim Übergang
zwischen privaten Netzen und dem Internet in aller Regel nicht: Dort ist eine Firewall im Einsatz, die üblicherweise
nach der Deny-all-Strategie alle Kommunikation blockiert, die nicht explizit freigegeben ist.
Auf diese Weise wird man im allgemeinen für eine neue Applikation eine Anpassung an der Firewall vornehmen müssen -
es sei denn, die Applikation gibt sich Mühe, wie ein Dienst auszusehen, der üblicherweise freigegeben ist. Das tun
zunehmend viele Applikationen und geben sich durch Benutzung von http als Transportprotokoll als Webclient und Webserver
aus, was in vielen Firewalls direkt freigegeben ist. Doch dies ist Stoff für einen anderen Artikel.
In diesem Artikel möchte ich davon schreiben, wie die Spezifikation aussehen soll, damit der Firewalladmin auch weiß,
was er für eine neue Applikation in seiner Firewall freischalten soll. In vielen Dokumentationen über (freie oder
kommerzielle) Software findet man Listen von Ports, die mehr oder weniger vollständig und mehr oder weniger korrekt
sind. Leider trifft in den meisten Fällen das “weniger” zu.
Doch zunächst ein paar Grundlagen zum Thema TCP/IP. Damit man in der Firewall nicht zu viel freigeben muss, sollten
die notwendigen Kommunikationsbeziehungen möglichst eng beschrieben sein. Eine TCP/IP-Kommunikationsbeziehung fast
aller Mainstreamprotokolle beschreibt ein Fünftupel, bestehend aus Source-IP, Destination-IP, Protokoll, Source-Port
und Destination-Port.
Wenn ein Client C mit einem Server S sprechen möchte, dann ist das Protokoll, das er dafür benutzen möchte, in der
Applikation festgelegt. TCP wird für Datenübertragung benutzt, bei der man sich darauf verlassen möchte, dass die
Daten bei ihrer Ankunft am Ziel korrekt und vollständig sind, und bereit ist, dafür erhöhten Netzaufwand und
unregelmäßige Übertragungszeiten in kauf zu nehmen. UDP benutzt man üblicherweise, wenn es sich nur um winzige
Datenmengen (z.B. DNS-Anfragen und ihre Antworten) handelt oder es eher darauf ankommt, dass Daten entweder gar nicht
oder halbwegs zeitlich passend ankommen (Streaming, Telefonie etc).
In beiden Fällen würfelt sich der Client normalerweise einen seiner freien Ports aus und benutzt diesen als
Source-Port. Der Destination-Port ist meist mit der Applikation festgelegt und bestimmt auf der Seite des Empfängers
den Prozess, bei dem das Paket abgeliefert werden soll. In der Programmierung funktioniert das so, dass der
Serverprozess sich an den von ihm gewünschten Port bindet, ihn sozusagen “abhört” (im englischen nennt man
das passender “listen on the port”) und alle dorthin gesendeten Daten empfangen kann.
So hat eine DNS-Anfrage üblicherweise das Protokoll UDP (Protokollnummer 17), die Destination-IP des Nameservers und
den Destination-Port 53. Der Server dreht bei seiner Antwort das Tupel um und sendet seine Antwort mit Source-Port 53
mit der IP des Clients als Destination-IP und der vom Client als seinen Source-Port gewählten Portnummer als
Destination-Port. Das sieht “auf dem Kabel” in etwa so aus:
01: proto=UDP srcip=192.168.218.21 srcport=49221 dstip=206.251.36.94 dstport=53
02: proto=UDP srcip=206.251.36.94 srcport=53 dstip=192.168.218.21 dstport=49221
Hier hat sich der Client also den Port 49221 gewürfelt. Die Antwort des Servers kommt bei der Client-Applikation an,
weil sie an den Port 49221 geschickt wurde - der Client kann also die Antwort anhand des Destination-Ports des
Antwortpaketes der Frage zuordnen.
Bei TCP (Protokollnummer 6) ist’s im Prinzip dasselbe, nur dass schon der Verbindungsaufbau (also bevor
überhaupt ein Bit Nutzdaten geflossen sind) aus drei Paketen (dem sogenannten Dreiwegehandshake) besteht. Hier dasselbe
für TCP und eine kurze http-Anfrage:
01: proto=TCP flags=SYN srcip=192.168.218.21 srcport=37734 dstip=81.169.179.176 dstport=80
02: proto=TCP flags=SYN,ACK srcip=81.169.179.176 srcport=80 dstip=192.168.218.21 dstport=37734
03: proto=TCP flags=ACK srcip=192.168.218.21 srcport=37734 dstip=81.169.179.176 dstport=80
04: proto=TCP flags= srcip=192.168.218.21 srcport=37734 dstip=81.169.179.176 dstport=80
05: proto=TCP flags= srcip=81.169.179.176 srcport=80 dstip=192.168.218.21 dstport=37734
06: proto=TCP flags= srcip=81.169.179.176 srcport=80 dstip=192.168.218.21 dstport=37734
07: proto=TCP flags= srcip=192.168.218.21 srcport=37734 dstip=81.169.179.176 dstport=80
08: proto=TCP flags=FIN srcip=81.169.179.176 srcport=80 dstip=192.168.218.21 dstport=37734
09: proto=TCP flags= srcip=192.168.218.21 srcport=37734 dstip=81.169.179.176 dstport=80
10: proto=TCP flags=FIN srcip=192.168.218.21 srcport=37734 dstip=81.169.179.176 dstport=80
11: proto=TCP flags= srcip=81.169.179.176 srcport=80 dstip=192.168.218.21 dstport=37734
Das bedarf dann doch einer kleinen Erklärung: Die Pakete 01 bis 03 sind der Dreiwegehandshake für den
Verbindungsaufbau (SYN, SYN/ACK, ACK). In Paket 04 übermittelt der Client seinen http-Request, der in Paket 05 vom
Server quittiert wird. Paket 06 ist die (in ein Paket passende) Antwort; Paket 07 die dazu gehörende Quittung. Da der
Server keine weiteren Daten zu übermitteln hat, signalisiert er in Paket 08 den Wunsch des Verbindungsabbaus (FIN).
Paket 09 ist die Quittung dafür; die Pakete 10 und 11 sind der Verbindungsabbau seitens des Clients und die dazu
passende Quittung.
Abgesehen von den oben gezeigten Standardfällen gibt es noch einen ganzen Haufen von Sonderfällen. So hat ESP als
IP-Protokoll keine Portnummern, so dass ein Paket einer ESP-Verbindung nur durch das Tripel Source-IP, Destination-IP,
Protokollnummer 50 (ESP) klassifiziert werden kann. In einigen wenigen Protokollen spielt die Absenderportnummer eine
Rolle; und wieder andere wenige Protokolle benutzen TCP UND UDP, dies aber auf derselben Portnummer. Und dann
gibt es natürlich “kranke, firewallfeindliche” Protokolle wie ftp, SIP, nfs, amanda, RPC (inklusive
MS-RPC), bei denen die Portnummern für den “eigentlichen” Dienst beidseitig erst durch eine Unterhaltung
der beteiligten Systeme durch eine Steuerverbindung auf einem wohldefinierten Port ermittelt werden.
Wir sehen auch, dass im allgemeinen die Kommunikation zwischen zwei Systemen nahezu immer bidirektional stattfindet.
Nur in ganz wenigen Sonderfällen (von denen mir gerade partout keiner einfallen möchte) versendet die eine Seite ein
Paket, ohne sich jemals für eine Antwort oder Quittung zu interessieren. Glücklicherweise braucht man sich darum in
der Regel heutzutage bei halbwegs aktuellen Firewalls nicht mehr zu kümmern: Es reicht, wenn man für eine erwünschte
Kommunikationsbeziehung das jeweils erste Paket erlaubt; die direkt oder indirekt dazu gehörenden Pakete werden von der
Firewall automatisch als solche erkannt und automatisch durchgelassen. Das trifft meist auf die direkten Antwortpakete
zu, aber bei besseren Systemen auch auf kompliziertere Fälle wie den unabhängigen Datenkanal bei “kranken”
Protokollen wie ftp oder zur Verbindung gehörende ICMP-Fehler wie “host unreachable” oder
“fragmentation needed”.
Für die beiden oben zitierten Paketdumps reichen also zwei Regeln:
proto=UDP srcip=192.168.218.21 srcport=49221 dstip=206.251.36.94 dstport=53
proto=TCP flags=SYN srcip=192.168.218.21 srcport=37734 dstip=81.169.179.176 dstport=80
Halt, stop. Da ist noch was zu viel: Der Source-Port wird vom Client jeweils neu ausgewürfelt; wir dürfen die
Firewallregeln also nicht ganz so eng fassen, wenn der Dienst auch beim zweiten Zugriffsversuch noch funktionieren
soll:
proto=UDP srcip=192.168.218.21 dstip=206.251.36.94 dstport=53
proto=TCP flags=SYN srcip=192.168.218.21 dstip=81.169.179.176 dstport=80
Wie wir alle wissen, benutzt DNS im Normalfall UDP, fällt aber bei manchen Fehlern auf das (langsamere, aufwändigere)
TCP zurück - beispielsweise wenn die Antwort nicht mehr in ein Antwortpaket passt. Also braucht’s noch eine
dritte Regel, die den DNS-Sonderfall abdeckt:
proto=TCP flags=SYN srcip=192.168.218.21 dstip=206.251.36.94 dstport=53
Bei TCP ist es zweckmäßig, nur “erste” Pakete durchzulassen, die auch korrekterweise das SYN-Flag gesetzt
haben. Sonst lässt man im Zweifel Pakete bis zum Host durch, von denen wir schon wissen, dass sie nicht koscher sind,
und dass der Host sie eh nur verwerfen wird.
Auf Unix-ähnlichen Systemen gibt es noch eine weitere Unterscheidung: Die Ports 1-1023 bleiben dem Benutzer root
vorbehalten; nur Prozesse, die als root laufen, dürfen diese Ports des lokalen Systems verwenden. Man sieht deswegen in
Firewallregeln wie den oberen oft die Begrenzung auf die “hohen” Ports ab 1024. Eine solche Begrenzung
bringt aber keine zusätzliche Sicherheit, denn ein Prozess, der einen der “niedrigen” Ports benutzen darf,
könnte genauso gut einen “hohen” Port verwenden. Ich empfehle deswegen das KISS-Prinzip und den allgemeinen
Verzicht auf Beschränkungen des Source-Ports bei so einfachen Regeln.
Aus diesen Informationen dürfte klar sein, dass an Herstellerdokumentationen “erlauben Sie Port 80
TCP/UDP” oder “Wir benutzen die Ports 514 und 443 incoming und outgoing” oder “benötigt: Port
1024-65535” wenig Sachkenntnis beteiligt war. Wer stur nach solchen Regeln arbeitet, reißt sich große
Sicherheitslöcher in seine Infrastruktur und ist nur noch durch das vielleicht vorhandene NAT vor den fatalen Folgen
solcher Konfigurationsfehlern geschützt - oder eben nicht.
Je länger die Portliste, desto höher ist die Wahrscheinlichkeit, dass manche dieser Ports nur noch aus historischen
Gründen in der Liste stehen und von der Applikation seit etwa 1948 nicht mehr genutzt werden. Gerne sieht man das bei
Applikationen, die unter Windows entweder Laufwerke mappen wollen oder in irgend einer Art mit dem Active Directory
sprechen: Microsoft hat diese beiden Zugriffsmethoden in den letzten zehn Jahren mehrfach umspezifiziert und jedes Mal
neue Ports verwendet, und die älteren Portnummern braucht man nur, wenn auch noch ältere Versionen im Einsatz sind. In
aktuellen Netzen reißt man sich damit potenzielle Sicherheitslöcher, denn auch die aktuellen Server lauschen
natürlich im Interesse der Rückwärtskompatibilität auch auf den “älteren” Ports.
Viele andere Ports aus solchen ellenlangen Listen werden nur dann benötigt, wenn man ein bestimmtes obskures Feature
der Software verwendet. Oder ein Eintrag in einer Portliste ist schlicht und einfach inkorrekt. Auch schon passiert: Ein
essentiell wichtiger Port steht aus irgend einem Grund nicht in der Liste und lässt die Applikation auch dann
subtil versagen, wenn man die Portdokumentation sklavisch in Firewallregeln konvertiert hat.
Deswegen lautet meine Empfehlung ganz klar, vom Hersteller einer Software gelieferte Portlisten komplett zu ignorieren
und aus tcpdump und Firewall-Log herauszudestillieren, welche Kommunikationsbeziehungen die Applikation im gegebenen
Anwendungsszenario wirklich zu benutzen gedenkt. Das funktioniert allerdings selten auf Anhieb, weil oftmals der
nächste Port erst benutzt wird, wenn der Zugriff auf den erstfreigegebenen Port erfolgreich war. Man ist also gut
beraten, wenn man die Applikation entweder selbst bedient oder den Anwender vorher darauf vorbereitet, dass man mehrere
Iterationen von “probier’s nochmal, ah ja, moment” benötigen wird bis die Applikation benutzbar sein
wird - denn sonst wird einem diese Trial-and-Error-Methode gerne als unstrukturiertes oder unfähiges Vorgehen
missinterpretiert. Außerdem muss man darauf vorbereitet sein, im Rahmen dieser Vorgehensweise auch die obskureren
Funktionen der Software bis hin zum “Agent-Update auf dem Client” auszuprobieren, denn sonst fällt einem
die Sicherheitspolicy vielleicht irgendwann im laufenden Betrieb schmerzhaft auf den Fuß.
Wenn man die aufwändige Phase einmal abgeschlossen hat, wird man nicht selten mit einem schlanken, gut dokumentierten
Regelwerk belohnt, von dem man weiß, dass es keine unnötigen Risiken enthält. Außerdem bekommt man durch die Arbeit
mit dem Firewall-Log und tcpdump ein Gefühl für die Kommunikation und genug Erfahrung mit den Tools, um später im
Fehlerfall souverän mit den Tools umgehen zu können und deren Ausgabe sinnvoll interpretieren zu können. Und es
erspart einem unnötige Aktionen mit der Glaskugel um zu raten, was der Hersteller mit seiner unvollständigen
Dokumentation, die nicht selten stur aus einer Portliste ohne Protokoll- und Richtungsangabe besteht, wohl gemeint haben
könnte.
Abschließend noch eine Wunschliste: Wenn Ihr den Firewallbetreuern Eurer Anwender einen großen Gefallen tut, dann
beschreibt die Kommunikationsbeziehungen Eurer Software vollständig. Das bedeutet also im Zweifel eine Liste wie hier:
name=DNS proto=(UDP/TCP) srcip=(1) dstip=(2) dstport=53
name=http proto=TCP srcip=(1) dstip=ivanova.notwork.de,*.zugschlus.de dstport=80
name=amanda-control proto=UDP srcip=client dstip=server dstport=10080
name=amanda-data proto=TCP srcip=client srcport=50000 dstip=server dstport=50000-50100
(1)=IP-Adresse des lokalen Systems
(2)=Full Service Resolver für das lokale System
Dokumentation gehört zum Regelwerk dazu, denn man möchte auch in zwei Jahren noch verstehen, warum man das, was da
drin steht, auch reingeschrieben hat. Deswegen liefert Eurem Firewaller bitte auch, wie das System heißt, für das da
die Regeln eingetragen werden sollen, und was es tut. Er wird dann noch das aktuelle Datum und Euren Namen
dazuschreiben, und dann ist die Dokumentation brauchbar.
Wenn es sich um ein Testsystem handelt, ist es sinnvoll, die damit verbundenen Regeln zeitlich zu beschränken. Dazu
muss der Firewaller aber wissen, dass es ein Test ist, und wie lange er voraussichtlich dauern wird. Manche
Firewallsysteme können bei jeder Regel sofort ein Verfallsdatum dazuschreiben. ab der die Regel automatisch nicht mehr
angewendet wird. Sonst muss man das halt manuell mit Kommentaren im Regelwerk machen. Geht auch.