Das Filtern von IPv6 ist eigentlich nicht so anders wie bei IPv4. Allerdings muss man bei ICMP ein bisschen umdenken. Unter Anderem ist Path MTU Discovery quasi “mandatory” und manche Dinge, die man früher einfach doof weggedroppt hat, muss man jetzt erlauben, sonst gehen nämlich unter Umständen auch mal Dinge nicht. Und man weiß nicht warum, bis man feststellt, dass das Logging aus ist.
Alles schon mal vorgekommen. Zwinker.
Die hier vorgeschlagenen Firewall-Regeln basieren auf meinen eigenen Erfahrungen aus der Praxis, auf den Empfehlungen der IETF in RFC 4890 sowie aus dem, was ich mir so erlesen habe.
Literatur
Apropos lesen. Die hier kann ich empfehlen:
- “Planning for IPv6” von Silvia Hagen
- “IPv6 Grundlagen” von Silvia Hagen, Link siehe oben
- “IPv6 Essentials” von Silvia Hagen und Vint Cerf ist im Prinzip das gleiche in englisch
- “IPv6 Security” von Scott Hogg und Eric Vyncke
Gibt noch mehr gute Bücher zu dem Thema. Ich persönliche favoriere inzwischen den Ansatz, nur so ein Übersichts-Werk (z.B. “Planning for IPv6”) zu lesen und sich den Rest über die entsprechenden RFCs zu erarbeiten. Das ist ein bisschen mehr Recherche-Arbeit und man muss in Englisch sattelfest sein, lohnt sich aber.
Im Folgenden gehe ich davon aus, dass hier nur weiter liest, der sich zumindest mal die IPv6 Grundlagen angeschaut hat und jetzt eigentlich nur noch wissen will, wie man das filtert.
Software
Wie immer ist das Regelwerk Linux-Only und wie immer ist ferm als iptables-Wrapper im Einsatz. Wer nftables bevorzugt, wird in der Lage sein, das umzuschreiben. Syntax und Aufbau des Regelwerks ist ähnlich.
Getestet habe ich das mit aktuellen Debian-Versionen (11 und 12) sowie mit Centos, da allerdings ohne ferm sondern “nur” mit nftables. Im Prinzip ist die Linux-Variante aber völlig schnuppe, so lange es iptables mit ferm oder eben nftables als native Pakete gibt.
Server oder Router?
Man muss bei ICMPv6 differenzieren, ob man an einem Node (also Server oder anderes Endgerät) oder einem Router (oder Firewall, Bridge, Load Balancer etc.) filtert. Die für die Funktion eines Hosts notwendigen Dienste sind ein wenig anders, als die Dienste, die ein komplettes Netzwerk benötigt und die somit von einem “Router” weitergeleitet werden müssen.
Exkurs: ICMPv6 Service Namen in Firewall-Regeln
Will man im Regelwerk sprechende Namen anstatt Typ und Code verwenden, so braucht man da eine Zuordnung, die die IANA sehr übersichtlich mit Links zu den entsprechenden RFCs auf ihrer Website veröffentlicht hat. Was davon man im Regelwerk mit Namen ansprechen kann und was nicht, verrät einem ip6tables an der Kommandozeile:
ip6tables -p ipv6-icmp -h
[...]
Valid ICMPv6 Types:
destination-unreachable
no-route
communication-prohibited
beyond-scope
address-unreachable
port-unreachable
failed-policy
reject-route
packet-too-big
time-exceeded (ttl-exceeded)
ttl-zero-during-transit
ttl-zero-during-reassembly
parameter-problem
bad-header
unknown-header-type
unknown-option
echo-request (ping)
echo-reply (pong)
router-solicitation
router-advertisement
neighbour-solicitation (neighbor-solicitation)
neighbour-advertisement (neighbor-advertisement)
redirect
Variablen
Im ersten Schritt werden (in der ferm.conf) die Variablen definiert. Wir lassen hier (bewusst) mal das Thema ULA aussen vor und verwenden einen für Demonstrationszwecke definierten IPv6 Adressraum in diesem Beispiel, aus dem wir zwei Slash-48-Netze raus schnitzen, was für unsere Zwecke völliger Overkill ist. 😁
Ich persönlich halte es für guten Stil, in Skripten, Programmen und Configs möglichst sprechende Namen zu verwenden, was beim Thema Multicast Listener Discovery leider nicht geht, da der Linux-Kernel die Services nicht namentlich kennt.
@def $REMOTE_HOST = ( 2001:db8:1::d00f );
@def $LOCALNET = ( 2001:db8:18::/48 );
@def $LINKLOCAL = ( fe80::/10 );
@def $IPV6_ECHO = ( echo-request echo-reply );
@def $IPV6_PMTU = ( destination-unreachable packet-too-big time-exceeded parameter-problem );
@def $IPV6_RAND = ( router-solicitation router-advertisement neighbour-solicitation neighbour-advertisement redirect );
@def $IPV6_MLD = ( 130 131 132 );
@def $IN_PORTS = ( ssh http https );
IPv6 Regelwerk für einen Host
Das erste Beispiel ist ein Regelwerk für einen Host (Webserver, Mailserver etc.), daher schauen wir uns nur die INPUT Chain an. Da wir später mit hashlimits arbeiten, muss das Erlauben von Antwortpaketen auf ICMPv6-Requests explizit abgeschaltet werden (Zeile “ESTABLISHED RELATED”). Sonst funktioniert das Traffic-Shaping nicht.
domain ip6 {
table filter {
chain INPUT {
policy DROP;
mod state state INVALID DROP;
proto ( tcp udp ) mod state state (ESTABLISHED RELATED) ACCEPT;
if lo ACCEPT;
Ping und Path MTU Discovery mit Limit
Um absichtliche oder unabsichtliche DoS-Angriffe zu verhindern, schränken wir die maximale Anzahl gleichzeitiger Verbindungen bei bestimmten Services ein. Dazu verwenden wir die oben (“hashlimits”) erwähnten Iptables-Extensions.
proto ipv6-icmp {
icmp-type ( $IPV6_ECHO $IPV6_PMTU ) {
mod hashlimit hashlimit 3/sec hashlimit-burst 5 hashlimit-mode ( dstip srcip ) hashlimit-name icmpv6 hashlimit-htable-expire 30000 ACCEPT;
}
}
Dass das Limit mehr oder weniger funktioniert, testen wir unter Linux mit fping, wobei 2001:db8:18::f00d die Adresse des Servers ist, auf dem die Firewall läuft:
user@remote:~# fping -6 2001:db8:18::f00d -l -p 100
2001:db8:18::f00d : [0], 64 bytes, 0.552 ms (0.552 avg, 0% loss)
2001:db8:18::f00d : [1], 64 bytes, 0.438 ms (0.495 avg, 0% loss)
2001:db8:18::f00d : [2], 64 bytes, 0.432 ms (0.474 avg, 0% loss)
2001:db8:18::f00d : [3], 64 bytes, 0.464 ms (0.471 avg, 0% loss)
2001:db8:18::f00d : [4], 64 bytes, 0.437 ms (0.465 avg, 0% loss)
2001:db8:18::f00d : [5], 64 bytes, 0.478 ms (0.467 avg, 0% loss)
2001:db8:18::f00d : [6], timed out (0.467 avg, 14% loss)
2001:db8:18::f00d : [7], 64 bytes, 0.468 ms (0.467 avg, 12% loss)
2001:db8:18::f00d : [8], timed out (0.467 avg, 22% loss)
2001:db8:18::f00d : [9], timed out (0.467 avg, 30% loss)
[...noch mehr Paketverlust...]
2001:db8:18::f00d : [76], timed out (0.456 avg, 64% loss)
2001:db8:18::f00d : [77], 64 bytes, 0.388 ms (0.454 avg, 64% loss)
^C
2001:db8:18::f00d : xmt/rcv/%loss = 78/28/64%, min/avg/max = 0.376/0.454/0.659
In der Input Chain droppen ist zwar nicht das effizienteste Mittel und gegen DDoS-Angriffe hilft das auch nur so ein bisschen bis gar nicht aber als Auditor-Nebelkerze (und zwar nur als das) geht das. Wenn man da ein kritisches System hat, muss man das natürlich anders machen. Das “einfachste” wäre dann, in der Prerouting Chain zu filtern…
Router und Neighbour Discovery
Router und Neighbour Discovery (RFC 2461) sind zwei essentielle Dienste, die das aus IPv4 bekannte ARP (Nein, nicht den Hans) ersetzen und dafür sorgen, dass man sich in “einfachen” Netzen nicht mehr um statische Routen kümmern muss. Um hier alle Pakete zu verwerfen, die nicht Standard-Konform sind, setzen wir folgende Eingeschaften explizit:
- ICMP Type Codes: Nur die in $IPV6_RAND angegebenen
- Quell-Adresse muss aus dem lokalen Netz sein (Entweder Link Local oder lokal konfiguriertes Netz)
- Das Hop Limit muss 255 sein
mod hl hl-eq 255 saddr ( $LINKLOCAL $LOCALNET ) proto ipv6-icmp icmp-type $IPV6_RAND ACCEPT;
Multicast Listener Discovery
Ähnliches gilt für Multicast Listener Discovery (RFC 3810): Hier muss das Hop-Limit 1 (Eins), die Absender-Adresse eine Link Local und der Service in $IPV6_MLD definiert sein.
mod hl hl-eq 1 saddr $LINKLOCAL proto ipv6-icmp icmp-type $IPV6_MLD ACCEPT;
Ganz normale IP-Freischaltung
Ja, auch ganz popelige Dienste wie SSH und HTTP kan man freischalten und das funktioniert noch genauso wie in IPv4:
saddr $REMOTE_HOST proto tcp dport $IN_PORTS ACCEPT;
Auch hier liesse sich per Hashlimit/Connlimit die maximale Anzahl der Pakete pro Sekunde regeln. Mach ich jetzt aber nicht, weil mir gegen Textende immer die Schreiblust ausgeht und der Wodka auch irgendwann alle ist.
Default Deny
Für die Produktiv-Tests und in der “Trainigsphase” kann man das Logging verworfener Pakete mal anlassen. Wenn das System ein wenig “bekannter” ist, schaltet man das besser aus. Da wir in der Definition der INPUT Chain ein “Policy Drop” stehen haben, genügt es, die Zeile eifach zu löschen
proto ( tcp udp ipv6-icmp ) LOG log-prefix "[Firewall] v6 INPUT Chain Drop: ";
Der Teil mit “wie mach ich das an einer Firewall” kommt noch … stay tuned for more Rock ’n’ Roll …