Jak włączyć stabilne adresy prywatne w IPv6 na linux

Spis treści

Jakiś już czas temu opisywałem jak włączyć rozszerzenia prywatności IPv6 na Debianie (IPv6 Privacy Extensions) w przypadku korzystania z mechanizmu automatycznej konfiguracji adresacji hostów SLAAC (StateLess Address AutoConfiguration). Miało to za zadanie poprawić nieco prywatność osób podłączonych do internetu za sprawą protokołu IPv6, bo generowane adresy IP standardowo zawierają adresy MAC kart sieciowych (identyfikator EUI64). Parę dni temu dowiedziałem się, że w linux można również aktywować inny mechanizm zwany stabilnymi adresami prywatnymi (stable-privacy addresses), które to wykorzystują inny system przy generowaniu identyfikatorów interfejsów sieciowych. Ten mechanizm sprawia, że część adresu IPv6 odpowiedzialna za identyfikację hosta ma losowe, choć stabilne wartości, które nie mają nic wspólnego z adresem MAC karty sieciowej naszego komputera. W ten sposób możemy ukrócić śledzenie nas w sieci na podstawie adresu IPv6. Poniższy artykuł ma za zadanie pomóc skonfigurować nam te stabilne adresy prywatne na linux oraz pokazać w jaki sposób są one w stanie pomóc naszej prywatności.

Czym są stabilne adresy prywatne

Zadaniem stabilnych adresów prywatnych jest zapobieganie śledzeniu hostów (i ich użytkowników) w sieci na podstawie adresu IPv6. Gdy podłączymy się do innej sieci (w domu, w biurze, czy też w dowolnym innym miejscu), to zmianie ulega prefiks tej sieci wykorzystywany przy autokonfiguracji SLAAC. Niemniej jednak, część adresu IPv6 odpowiedzialna za identyfikację hosta pozostaje taka sama w każdej sieci, do której się podłączymy. W ten sposób serwisy WWW mogą nas bez problemu śledzić, bo są one w stanie zidentyfikować konkretnego hosta podpiętego do sieci IPv6 po jego identyfikatorze EUI64, który ma wbudowany adres sprzętowy MAC karty sieciowej. Jeśli teraz zaimplementujemy sobie stabilne adresy prywatne, to zmianie ulegnie identyfikator interfejsu (IID, Interface Identifier) za sprawą choćby zmiany identyfikatora sieci (np. ESSID w sieciach WiFi). W ten sposób wynikowy identyfikator interfejsu będzie inny dla każdej z odwiedzanych przez nas sieci. Inny identyfikator interfejsu oznacza również inny adres IPv6, bo część hosta za każdym razem będzie ulegać zmianie, tj. dla każdej nowej sieci będzie ten identyfikator inny. Przynajmniej taka jest teoria działania tego mechanizmu, bo jak możemy wyczytać w RFC7217, ten ficzer ze zmianą adresu IPv6 w zależności od sieci, do której się podłączymy, jest jedynie opcjonalny:

Network_ID: Some network-specific data that identifies the subnet to which this interface is attached -- for example, the IEEE 802.11 Service Set Identifier (SSID) corresponding to the network to which this interface is associated. Additionally, Simple DNA [RFC6059] describes ideas that could be leveraged to generate a Network_ID parameter. This parameter is OPTIONAL.

-- https://tools.ietf.org/html/rfc7217#section-5

Jak można się domyślać, póki co ta opcja nie jest zaimplementowana w kernelu linux'a (testowane na 5.8.1). Niemniej jednak, stabilne adresy prywatne są nas w stanie również uchronić przed różnymi technikami skanowania adresów IP wykorzystującymi przewidywalne identyfikatory EUI64. Chodzi generalnie o to, że identyfikatory OUI (Organizationally Unique Identifier) adresów MAC kart sieciowych mają może i te 24 bity ale każdy producent sprzętu ma przypisany konkretny OUI i w ten sposób te 24 bity są powszechnie znane. By utworzyć identyfikator EUI64, potrzebne są jeszcze dwie stałe wartości 0xff i 0xfe oraz losowe 24 bity, które dopełniają ten 64 bitowy (24+16+24) identyfikator interfejsu. Takie rozwiązanie ułatwia dość znacznie atakującemu wyszukiwanie indywidualnych adresów IP przy założeniu, że ofiara ma sprzęt jednego z tych popularniejszych producentów. W ten sposób spora część ochrony przeciw skanowaniu adresów wynikająca z ogromnej puli adresów IP, jaką daje IPv6 w stosunku do IPv4, jest tracona bezpowrotnie.

Adresy prywatne dodatkowo chronią nas przed wyciekiem informacji za sprawą osadzania adresów sprzętowych MAC w identyfikatorze interfejsu, co mógłby zostać wykorzystane do przeprowadzenia ataków na określone urządzenia. Warto też dodać, że różne identyfikatory interfejsów dla każdego skonfigurowanego adresu IPv6 sprawiają, że pozyskanie informacji na temat IID dla jednego stabilnego adresu nie wpływa w negatywny sposób na bezpieczeństwo czy prywatność innych stabilnych adresów skonfigurowanych dla innych prefiksów.

Kolejna sprawa to fakt, że stabilne adresy prywatne mogą być niezależne od sprzętu (NIC). Wygenerowane adresy IPv6 nie ulegną więc zmianie, np. po wymianie karty sieciowej w komputerze. W ten sposób nie będziemy musieli zmieniać konfiguracji swojego linux'a czy też domowej sieci. Warto tu jednak zaznaczyć, że dwa różne systemy operacyjne działające na tym samym hoście w konfiguracji multi/dual boot mogą mieć przydzielane różne adresy IPv6.

Stabilne adresy prywatne są podobne do rozszerzeń prywatności IPv6 (IPv6 Privacy Extensions) ale to są dwa różne mechanizmy poprawiające prywatność przy dostępie do sieci IPv6. Żadne z nich nie zastępuje drugiego i nie czyni go przestarzałym. Oba te mechanizmy mogą i powinny współistnieć jednocześnie.

Konfiguracja stabilnych adresów prywatnych na Debianie

Implementacja protokołu IPv6 na świecie póki co dość mocno kuleje i przez ostatnie 1,5 roku ilość maszyn operujących na tym protokole zwiększyła się zaledwie o jakieś 5-8% i obecnie wynosi ~30%. W Polsce, IPv6 to najwyraźniej jakaś abstrakcja, bo zaledwie 12% maszyn jest podłączonych do sieci po IPv6. W ten sposób dość ciężko jest uzyskać dostęp do tej sieci, no i ja go aktualnie nie posiadam. Niemniej jednak, wsparcie dla IPv6 u naszego ISP nie jest konieczne by zrozumieć jak działają stabilne adresy prywatne i jak je sobie skonfigurować, tak by móc zrobić z nich użytek w przyszłości, gdy IPv6 stanie się dla nas dostępne.

Trochę szkoda, że póki co nie da rady zabezpieczyć prywatności użytkowników przy roamingu między różnymi sieciami, głównie WiFi, ale na szczęście nie jest tak źle jak mogłoby się na pierwszy rzut oka wydawać. Przy odrobinie determinacji możemy w mniejszym lub większym stopniu to zadanie zrealizować. Niemniej jednak, trzeba pierw w systemie skonfigurować sobie stabilne adresy prywatne, bo domyślnie ten ficzer jest wyłączony. Zatem do dzieła.

Tak wygląda przykład interfejsu sieciowego bez skonfigurowanych stabilnych adresów prywatnych:

# ip addr show dev bond0
2: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:21:cc:c3:05:b0 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.150/24 brd 192.168.1.255 scope global dynamic bond0
       valid_lft 86309sec preferred_lft 86309sec
    inet6 fd9d:40f3:50f5:0:221:ccff:fec3:5b0/64 scope global dynamic mngtmpaddr
       valid_lft forever preferred_lft forever
    inet6 fe80::221:ccff:fec3:5b0/64 scope link
       valid_lft forever preferred_lft forever

Jak widać, mój ISP nie wie co to IPv6 i udostępnia mi jedynie połączenie IPv4. Te widoczne wyżej linijki z inet6 to są lokalne adresy IPv6 uzyskiwane nawet jeśli host nie ma możliwości globalnej komunikacji z wykorzystaniem protokołu IPv6 (wystarczy, że domowy router posiada wsparcie dla IPv6). W ten sposób mamy fe80::/64 , który wskazuje na adres linku lokalnego (link-local address) oraz fd00::/8 wskazujący na unikalny adres lokalny (ULA, Unique Local Address). Może i nie mamy tutaj globalnego adresu IPv6 ale te dwa lokalne adresy IPv6 nam w zupełności wystarczą by zademonstrować działanie stabilnych adresów prywatnych.

Jak można było zauważyć wyżej w wyjściu ip , adres MAC karty sieciowej od tego interfejsu to 00:21:cc:c3:05:b0 . Z niego został zbudowany identyfikator EUI64 0221:ccff:fec3:05b0 . Bez większego problemu można ten identyfikator zamienić na MAC i w ten sposób poznać adres sprzętowy karty sieciowej komputera. Ten identyfikator EUI64 byłby obecny też w globalnym adresie IPv6, gdybyśmy go tylko posiadali.

Parametry addr_gen_mode oraz stable_secret w sysctl

Generowanie stabilnych adresów prywatnych na linux można skonfigurować w pliku /etc/sysctl.conf . Ogólnie rzecz ujmując to interesują nas dwa parametry, tj. addr_gen_mode oraz stable_secret . W różnych linux'ach te parametry przyjmują inne wartości. W Debianie, parametr addr_gen_mode ma standardowo wartość 0 , natomiast próżno szukać w konfiguracji sysctl parametru stable_secret (o tym za chwilę).

Parametr addr_gen_mode określa w jaki sposób są generowane adresy typu link-local oraz te automatycznie konfigurowane (SLAAC). W przypadku ustawienia tutaj wartości 0 , adresy IPv6 będą generowane w oparciu o identyfikator EUI64 i to jest to domyślne zachowanie, które zagraża nieco naszej prywatności.

Ustawienie addr_gen_mode na 1

Gdy ustawimy wartość addr_gen_mode na 1 , to system zaprzestanie generowania adresów typu link-local . Brak adresu link-local sprawia, że protokoły takie jak NDP (Neighbor Discovery Protocol) czy DHCPv6 przestaną nam działać. W ten sposób nie damy rady już automatycznie skonfigurować globalnego adresu IPv6 na interfejsie sieciowym (potrzebna będzie manualna konfiguracja adresacji).

net.ipv6.conf.bond0.addr_gen_mode = 1

Poniżej jest przykład tego samego interfejsu, który mieliśmy wcześniej ale z ustawionym parametrem addr_gen_mode na 1 :

# ip addr show dev bond0
2: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:21:cc:c3:05:b0 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.150/24 brd 192.168.1.255 scope global dynamic bond0
       valid_lft 86321sec preferred_lft 86321sec

No jak widać, w tym przypadku został nam jedynie adres IPv4.

Ustawienie addr_gen_mode na 2/3 i stable_secret

W przypadku ustawienia wartości addr_gen_mode na 2 , stabilne adresy prywatne generowane będą wykorzystując wartość określoną w parametrze stable_secret (zgodnie z RFC7217). W przypadku nieustawienia stable_secret , w zasadzie zachowanie tego mechanizmu będzie takie samo jak przy wartości 1 , tj. brak adresów lokalnych. Trzeba zatem się upewnić, że stable_secret został wygenerowany. Standardowo w Debianie nie ma w sysctl takiego parametru jak stable_secret . Pojawi się on dopiero wtedy, gdy system go wygeneruje, a wygeneruje go w momencie, gdy ustawi się w addr_gen_mode wartość 3 lub też ręcznie ustawimy wartość sekretu dla danego interfejsu, przykładowo:

#net.ipv6.conf.all.stable_secret = e158:669a:0b27:307a:931d:aef0:bb70:b9aa
net.ipv6.conf.default.stable_secret = e158:669a:0b27:307a:931d:aef0:bb70:b9aa
net.ipv6.conf.bond0.stable_secret = e158:669a:0b27:307a:931d:aef0:bb70:b9aa

Teoretycznie parametr stable_secret ma również odpowiednik all i default ale do all nie da rady nic zapisać. Jeśli zaś chcemy ustawić sekret dla każdego nowego interfejsu w systemie, to korzystamy z default , a jeśli tylko dla określonego interfejsu, to podajemy jego nazwę.

Z chwilą wygenerowania sekretu, kernel nie będzie już go automatycznie generował ponownie, przynajmniej podczas pracy systemu. By ten sekret został ponownie wygenerowany, trzeba będzie zrestartować maszynę. Jeśli zaś chcemy ten sekret zmienić podczas pracy systemu, to przy pomocy sysctl trzeba przepisać wartość parametru stable_secret ręcznie.

Gdy mamy wygenerowany sekret, to nie ma znaczenia już czy wartość parametru addr_gen_mode jest ustawiona na 2 lub 3 , bo ten mechanizm generowania stabilnych adresów prywatnych będzie zachowywał się od tego momentu dokładnie tak samo:

net.ipv6.conf.all.addr_gen_mode = 2
net.ipv6.conf.default.addr_gen_mode = 2

Te powyższe parametry zmieniają politykę generowania adresów IPv6 dla każdego interfejsu w systemie. Jeśli chcemy jedynie zmienić politykę generowania adresów dla konkretnego interfejsu, to w miejscu all albo default trzeba podać nazwę interfejsu sieciowego, np. bond0 :

net.ipv6.conf.bond0.addr_gen_mode = 2

Jeśli podejrzymy teraz jeszcze raz nasz interfejs sieciowy, to zobaczymy coś takiego:

# ip addr show dev bond0
2: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:21:cc:c3:05:b0 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.150/24 brd 192.168.1.255 scope global dynamic bond0
       valid_lft 86395sec preferred_lft 86395sec
    inet6 fd9d:40f3:50f5:0:6e6f:2f3b:5a2:13cc/64 scope global dynamic mngtmpaddr stable-privacy
       valid_lft forever preferred_lft forever
    inet6 fe80::8c2a:9317:9f2e:76/64 scope link stable-privacy
       valid_lft forever preferred_lft forever

Widzimy tutaj, że przy pozycjach z adresami IPv6 ( inet6 ) pojawiły się tagi stable-privacy . Oznaczają one, że mamy do czynienia ze stabilnymi adresami prywatnymi, co możemy też poznać po innym identyfikatorze dla każdego z tych dwóch adresów IPv6: 8c2a:9317:9f2e:0076 / 6e6f:2f3b:05a2:13cc vs. standardowy identyfikator EUI64 0221:ccff:fec3:05b0 wygenerowany na podstawie adresu MAC karty sieciowej. Gdyby linux posiadał wsparcie dla roamingu między sieciami, to dodatkowo dla każdej takiej sieci te adresy IPv6 byłyby inne.

Przeładowanie konfiguracji sysctl i restart sieci

Po dodaniu opisanych wyżej parametrów do pliku /etc/sysctl.conf i ustawieniu im odpowiednich wartości, musimy jeszcze przeładować konfigurację sysctl , tj. wydać w terminalu to poniższe polecenie, by zmiany weszły w życie:

# sysctl -p

Oczywiście by odczuć jakąkolwiek różnicę w zachowaniu systemu, trzeba położyć interfejs i go podnieść ponownie. Krótko mówiąc, trzeba zrestartować połączenie sieciowe.

# ifdown bond0
# ifup bond0

Zabezpieczenie pliku /etc/sysctl.conf

Trzeba także mieć na uwadze, że domyślnie każdy użytkownik w systemie jest w stanie odczytać plik /etc/sysctl.conf , w którym ten sekret będzie przechowywany. Dlatego też praktycznie każdy proces dowolnego użytkownika w systemie może poznać wartość parametru stable_secret, co czyni cały mechanizm ździebko bez sensu. Dlatego też powinniśmy zadbać o odpowiednie prawa dostępu do pliku /etc/sysctl.conf :

# chmod 600 /etc/sysctl.conf
# ls -al /etc/sysctl.conf
-rw------- 1 root root 216915 2020-08-17 20:58:25 /etc/sysctl.conf

Poprawa prywatności przy roamingu WiFi

Stabilne adresy prywatne są ciekawym mechanizmem poprawiającym naszą prywatność w sieciach IPv6. Szkoda tylko, że póki co w linux nie ma zaimplementowanego wsparcia dla roamingu sieci WiFi. Niemniej jednak, jeśli konfigurujemy osobisty laptop, którego zabieramy w podróż po świecie, to możemy spokojnie w addr_gen_mode ustawić 3 i zrezygnować z ustawiania sekretu w stable_secret . W taki sposób, za każdym razem jak tylko uruchomimy komputer ponownie, to wszystkie adresy IPv6 na interfejsach sieciowych ulegną zmianie i każdy, kto nas śledził w sieci, straci zwyczajnie trop. Trzeba jednak pamiętać, że wygenerowany sekret będzie stały do momentu restartu maszyny. Dlatego też dobrze jest co jakiś czas ten sekret przepisać ręcznie i zrestartować połączenie sieciowe. W przypadku każdego innego wykorzystania komputera, możemy ustawić addr_gen_mode na 2 oraz określić stały sekret. Raz ustawiony sekret nie powinien być zmieniany.

Jeśli jednak chcielibyśmy trochę zautomatyzować cały ten proces poprawy prywatności w sieciach WiFi, to możemy napisać prosty skrypt dla ifupdown , który będzie wywoływany chwilę przed podniesieniem interfejsu. Zadaniem tego skryptu będzie wygenerowanie nowego sekretu i uzupełnienie parametru stable_secret . By taką automatyzację wdrożyć, tworzymy plik /etc/network/if-pre-up.d/random-secret :

# touch /etc/network/if-pre-up.d/random-secret
# chmod +x /etc/network/if-pre-up.d/random-secret

i dodajemy do niego poniższą zawartość:

#!/bin/sh

RUN="yes"

if [ "$RUN" = "yes" ]; then

	if [ "$IFACE" = "wwan0" ] ||
	   [ "$IFACE" = "bond0" ] ||
	   [ "$IFACE" = "usb0" ]  ||
	   [ "$IFACE" = "eth0" ]  ||
	   [ "$IFACE" = "wlan0" ]; then

			secret=$(head -c16 </dev/urandom | \
					 xxd -p | \
					 fold -w4 | \
					 paste -sd':')
			sysctl -q -w net.ipv6.conf.$IFACE.stable_secret=$secret
	fi
fi

W przypadku, gdy zajdzie potrzeba wyłączenia tego skryptu, to zawsze możemy przestawić RUN="yes" na RUN="no" . Dodatkowo, możemy sobie określić, które z interfejsów w systemie będą mieć generowane stabilne adresy prywatne przez uzupełnienie warunku [ "$IFACE" = "wwan0" ] . Można naturalnie podać więcej interfejsów niż jeden.

I jeśli zaś chodzi o generowanie sekretu, to head -c16 </dev/urandom czyta 16 losowych bajtów (128 bitów) z urządzenia /dev/urandom . Następnie xxd -p przerabia te bity na zapis HEX i w taki sposób otrzymujemy ciąg typu 06294cadd1ead395f7cb634bd4da270a . Dalej mamy fold -w4 , który tnie tę jedną długą linijkę na kilka mniejszych, z których każda będzie miała 4 znaki, tj. 0629, 4cad, etc. Później paste -sd':' ma za zadanie te kolejne wiersze połączyć ze sobą używając : . I w taki oto sposób otrzymujemy sekret w postaci 0629:4cad:d1ea:d395:f7cb:634b:d4da:270a , który jest następnie podawany w sysctl . Generowane w ten sposób sekrety będą unikalne dla każdego z interfejsów sieciowych, nawet w przypadku, gdy jednocześnie konfigurujemy wiele z nich, np. za sprawą systemctl restart networking .

Sprawdźmy jak wygląda teraz zachowanie naszego linux'a przy restarcie połączenia sieciowego:

# ip addr show bond0
2: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
	link/ether 00:21:cc:c3:05:b0 brd ff:ff:ff:ff:ff:ff
	inet 192.168.1.150/24 brd 192.168.1.255 scope global dynamic bond0
	   valid_lft 43191sec preferred_lft 43191sec
	inet6 fd9d:40f3:50f5:0:9715:f78e:a22a:d5dd/64 scope global dynamic mngtmpaddr stable-privacy
	   valid_lft forever preferred_lft forever
	inet6 fe80::35a7:1164:74c1:87ca/64 scope link stable-privacy
	   valid_lft forever preferred_lft forever

# ifdown bond0
# ifup bond0

# ip addr show bond0
2: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
	link/ether 00:21:cc:c3:05:b0 brd ff:ff:ff:ff:ff:ff
	inet 192.168.1.150/24 brd 192.168.1.255 scope global dynamic bond0
	   valid_lft 43198sec preferred_lft 43198sec
	inet6 fd9d:40f3:50f5:0:a9e8:9496:ba2e:e035/64 scope global tentative dynamic mngtmpaddr stable-privacy
	   valid_lft forever preferred_lft forever
	inet6 fe80::de92:3cfd:25b8:c944/64 scope link stable-privacy
	   valid_lft forever preferred_lft forever

Jak widać, adresy IPv6 uległy zmianie po restarcie połączenia sieciowego i widać na żywym przykładzie, że możemy w dość prosty sposób pozbyć się identyfikatora sprzętowego adresu MAC z części identyfikującej hosta w adresie IPv6 i tym samym utrudnić dość mocno śledzenie nas w sieci.

Mikhail Morfikov avatar
Mikhail Morfikov
Po ponad 10 latach spędzonych z różnej maści linux'ami (Debian/Ubuntu, OpenWRT, Android) mogę śmiało powiedzieć, że nie ma rzeczy niemożliwych i problemów, których nie da się rozwiązać. Jedną umiejętność, którą ludzki umysł musi posiąść, by wybrnąć nawet z tej najbardziej nieprzyjemniej sytuacji, to zdolność logicznego rozumowania.