Container escape: namespaces, seccomp i AppArmor w praktyce
Techniczny model container escape bez exploitów: Linux namespaces, capabilities, seccomp, AppArmor, OCI, detekcja runtime i hardening hosta.
- AUTOR
- Karol Rapacz / CEO Breachroad · OSCP · PNPT
- PUBLIKACJA
- 21 kwietnia 2026
- CZAS CZYTANIA
- 22 min czytania
- TEMAT
- Chmura, infrastruktura i DevSecOps
Container escape oznacza przekroczenie granicy izolacji kontenera i uzyskanie niezamierzonego wpływu na host, runtime albo inne workloady. Nie jest nazwą jednej techniki. Może wynikać z podatności jądra lub runtime, ale równie często źródłem jest konfiguracja: tryb privileged, host namespaces, niebezpieczne capabilities, zamontowany socket runtime, urządzenia hosta albo zapisywalny wolumen z wrażliwą ścieżką.
Najważniejsza odpowiedź brzmi: kontener korzysta ze wspólnego jądra hosta, więc jego bezpieczeństwo zależy od kilku nakładających się kontroli. Namespaces ograniczają widoczność, cgroups zasoby, capabilities dzielą uprawnienia roota, seccomp filtruje syscalls, a AppArmor lub SELinux ogranicza operacje procesu. Obraz, runtime, kernel i orkiestrator muszą być aktualne. Wyłączenie jednej warstwy nie zawsze prowadzi do ucieczki, lecz zmniejsza margines bezpieczeństwa i wzmacnia kolejne błędy.
Container escape, breakout i zwykłe przejęcie aplikacji
Trzeba rozróżnić trzy skutki. Przejęcie procesu w kontenerze daje kod z jego UID, systemem plików, tokenami i siecią, ale nie dowodzi dostępu do hosta. Przekroczenie granicy workloadu może oznaczać odczyt współdzielonego wolumenu albo użycie tokenu do ataku na inne zasoby. Dopiero container escape daje niezamierzone wykonanie lub kontrolę w przestrzeni hosta bądź runtime.
Ta klasyfikacja chroni przed dwoma błędami raportowymi. Nie należy nazywać każdej powłoki w kontenerze „przejęciem węzła”. Z drugiej strony RCE aplikacji nie jest niskim ryzykiem tylko dlatego, że działa w kontenerze: proces może posiadać chmurową tożsamość, sekrety i sieciową ścieżkę do krytycznych usług.
Warstwowy model izolacji Linuksa
| Warstwa | Co ogranicza | Czego nie gwarantuje |
|---|---|---|
| Namespaces | widok procesów, mountów, sieci, IPC, hostname, użytkowników | odporności na podatność wspólnego jądra |
| cgroups | CPU, pamięć, I/O, PID i rozliczanie zasobów | kontroli dostępu do danych i syscalls |
| capabilities | dzieli tradycyjne prawa roota na mniejsze zdolności | bezpieczeństwa przy zbyt szerokim zestawie lub błędzie kernela |
| seccomp | dopuszcza lub blokuje wywołania systemowe i wybrane argumenty | pełnego modelu dostępu do plików i obiektów |
| AppArmor/SELinux | obowiązkowa polityka dostępu procesu | poprawności samej aplikacji i wszystkich reguł |
| user namespace/rootless | mapuje UID i ogranicza znaczenie roota kontenera | eliminacji wspólnego kernela i błędów konfiguracji |
| OCI runtime | tworzy proces, namespaces, mounty, urządzenia i polityki | bezpieczeństwa przy starej wersji lub niebezpiecznym configu |
Bezpieczeństwo wynika z kompozycji. Najlepszy test nie szuka jednego „magicznego” ustawienia, lecz sprawdza, czy kilka niezależnych kontroli ogranicza ten sam niepożądany skutek.
Linux namespaces: izolacja widoku, nie osobne jądro
Namespaces sprawiają, że proces widzi kontrolowaną część zasobów systemowych. Najważniejsze typy to:
- PID — oddzielne drzewo procesów;
- mount — odrębny widok punktów montowania;
- network — interfejsy, adresy, routing, porty i część stosu sieciowego;
- IPC — wybrane mechanizmy komunikacji międzyprocesowej;
- UTS — hostname i nazwa domeny;
- user — mapowanie UID/GID i capabilities w zakresie namespace;
- cgroup — widok hierarchii cgroups;
- time — wybrane zegary namespace.
Współdzielenie hostPID, hostNetwork albo hostIPC usuwa część separacji. Nie zawsze jest podatnością — agenty bezpieczeństwa i sieci mogą potrzebować takiego dostępu — ale wymaga wyższego zaufania, osobnego ServiceAccount i ścisłej kontroli obrazu. Mount namespace również traci znaczenie, jeśli workload dostaje zapisywalny dostęp do krytycznej ścieżki hosta.
User namespaces redukują wpływ UID 0 w kontenerze, mapując go na nieuprzywilejowany identyfikator hosta. Nie są jednak zamiennikiem seccomp, polityki MAC i aktualizacji kernela. Rootless Docker dodatkowo uruchamia daemon i kontenery bez roota, ograniczając skutki przejęcia runtime, ale ma wymagania i ograniczenia funkcjonalne opisane w dokumentacji rootless mode.
Linux capabilities i problem „prawie privileged”
Capabilities dzielą monolityczne uprawnienia roota. Runtime zwykle zachowuje ograniczony zestaw, a resztę usuwa. Ryzyko rośnie, gdy konfiguracja dodaje zdolności niezwiązane z funkcją aplikacji, szczególnie szerokie możliwości administracji systemem, modułów, urządzeń, sieci i śledzenia procesów.
Nie wystarczy przegląd pola cap_add. Trzeba sprawdzić permitted, effective, inheritable, bounding i ambient sets procesu oraz flagę no_new_privileges. Proces potomny może zmienić efektywny zestaw w zależności od pliku wykonywalnego, UID i reguł securebits. Bezpieczny baseline zaczyna od drop: ALL i dodaje tylko zdolność potwierdzoną testem funkcjonalnym.
Tryb privileged jest kategorią osobną. Runtime nadaje bardzo szerokie prawa, dostęp do urządzeń i łagodniejsze profile bezpieczeństwa. Nie powinien być skrótem naprawiającym błędy aplikacji. Jeżeli komponent rzeczywiście wymaga wysokich uprawnień, warto odseparować go na dedykowanych węzłach i ograniczyć jego kod, sieć oraz tożsamość.
Seccomp: redukcja powierzchni syscalls
Seccomp filter pozwala procesowi załadować filtr BPF, który ocenia numer syscalla, architekturę i dostępne argumenty. Filtr może zezwolić, zwrócić błąd, zakończyć proces, wygenerować pułapkę lub zastosować inne obsługiwane działanie. Seccomp nie jest sandboxem samym w sobie; dokumentacja kernela wprost wskazuje, że powinien współpracować z innymi mechanizmami.
Domyślny profil Dockera blokuje wybrane syscalls, zachowując model allowlisty odpowiedni dla typowych kontenerów. Dokumentacja seccomp Dockera zaleca pozostawienie profilu domyślnego, jeśli aplikacja nie ma uzasadnionej potrzeby zmiany. seccomp=unconfined poszerza powierzchnię kernela i powinno być wyjątkiem.
Test profilu powinien odpowiadać na trzy pytania:
- czy profil jest rzeczywiście załadowany dla działającego procesu;
- które syscalls aplikacja wykorzystuje w normalnym i awaryjnym przebiegu;
- czy zablokowanie niepotrzebnej klasy wywołań generuje obserwowalne zdarzenie bez przerywania usługi.
Profil tworzony wyłącznie z krótkiego trace może być zbyt wąski dla rzadkiej ścieżki, aktualizacji biblioteki lub obsługi sygnału. Należy testować go regresyjnie w CI i na canary deployment, a nie uczyć bezpośrednio na produkcji.
AppArmor: polityka dostępu oparta na ścieżkach
AppArmor przypisuje profil do programu i ogranicza pliki, capabilities, sieć, sygnały, mounty oraz inne operacje wspierane przez politykę. Tryb complain loguje naruszenia, ale ich nie egzekwuje; tryb enforce blokuje. Kontener bez właściwie załadowanego profilu może działać inaczej niż wskazuje manifest.
Docker AppArmor documentation opisuje domyślny profil docker-default, generowany i ładowany dla kontenerów. W orkiestratorze trzeba zweryfikować profil na każdym węźle, nazwę wskazaną workloadowi i zachowanie po aktualizacji. Polityka powinna chronić szczególnie ścieżki kernela, runtime, poświadczenia hosta i niepotrzebne narzędzia administracyjne.
AppArmor jest path-based, więc aliasy ścieżek, bind mounty i zmienny layout aplikacji wymagają testów. Nie należy kopiować profilu z innej dystrybucji bez sprawdzenia nazw plików i bibliotek.
OCI runtime i niebezpieczne punkty konfiguracji
OCI Runtime Specification standaryzuje m.in. namespaces, UID/GID mappings, urządzenia, resources, seccomp, masked paths i read-only paths. Implementacja runtime tworzy proces kontenera na podstawie bundle i konfiguracji.
W audycie sprawdzamy:
- wersję oraz poprawki
runc,containerd, CRI-O i kernela; - dostęp do socketów Docker, containerd, CRI lub kubelet;
privileged, host namespaces i hostPath;- urządzenia, device cgroup i szerokie capabilities;
- writable
/proc,/sys, cgroupfs i ścieżki runtime; - maskedPaths, readonlyPaths, read-only root filesystem i mount propagation;
no_new_privileges, seccomp oraz profil AppArmor/SELinux;- możliwość zmiany specyfikacji przez admission webhook, operator lub pipeline.
Socket runtime jest interfejsem zarządzającym, nie zwykłym plikiem. Workload mogący tworzyć uprzywilejowane kontenery przez daemon w praktyce przekracza zamierzoną granicę, nawet bez exploita kernela.
Najczęstsze ścieżki do wpływu na host
Ścieżki można pogrupować w cztery kategorie:
- błąd oprogramowania — podatność kernela, runtime, filesystemu lub komponentu orkiestracji;
- nadmierna konfiguracja — privileged, host namespaces, capabilities, urządzenia i host mounts;
- interfejs zarządzający — socket runtime, kubelet, API klastra lub panel węzła;
- łańcuch dostaw — złośliwy lub podmieniony obraz uruchomiony w zaufanym kontekście.
Ostatnia kategoria łączy runtime z bezpieczeństwem obrazów kontenerów i SBOM w formatach CycloneDX, SPDX oraz VEX. Podpis obrazu nie ogranicza jego capabilities, a seccomp nie potwierdza pochodzenia kodu — kontrole rozwiązują różne problemy.
Bezpieczna metodologia testu izolacji
1. Najpierw analiza konfiguracji
Zbieramy OCI spec, manifest workloadu, polityki admission, runtime, kernel, profile seccomp/MAC, mounty, devices i tożsamość. Porównujemy stan deklarowany z działającym procesem. Sama konfiguracja Helm nie dowodzi, że wszystkie repliki korzystają z tego samego profilu.
2. Laboratorium zgodne z produkcją
Testy zachowania kernela i runtime wykonujemy na odtworzonym węźle o tej samej wersji, konfiguracji i sterownikach. Nie uruchamiamy publicznych exploitów kernela na produkcji. Jeżeli CVE ma znaczenie, dowodem może być podatna wersja plus osiągalne warunki, a dynamiczną demonstrację ograniczamy do disposable lab.
3. Harmless canaries
Przygotowujemy niedostępne dla kontenera pliki-canary, zabronione połączenie i syscall testowy bez skutku biznesowego. Potwierdzamy odmowę oraz log seccomp/AppArmor. Nie próbujemy modyfikować hosta, ładować modułu ani uzyskiwać trwałości.
4. Test regresyjny
Po zaostrzeniu profilu uruchamiamy normalne ścieżki, start, shutdown, health checks, backup, diagnostykę i obsługę awarii. Kontrola, która łamie aktualizację lub reakcję na incydent, zostanie szybko wyłączona.
5. Retest na działającym workloadzie
Weryfikujemy efektywny UID, capabilities, NoNewPrivs, status seccomp, profil MAC, mounty i namespaces każdej klasy workloadu. Sprawdzamy również węzły zapasowe i pule autoscaling.
Detekcja prób container escape
Telemetria powinna łączyć zdarzenia procesu, kernela, runtime, Kubernetes i chmury. Sygnały wysokiej wartości obejmują:
- AppArmor
DENIED, zdarzenia seccomp i odmowy LSM; - nietypowe syscalls, namespace creation lub dostęp do
/proci/sys; - uruchomienie narzędzi administracyjnych, kompilatora lub debuggera w obrazie runtime;
- dostęp do socketu runtime, kubeletu i metadanych chmurowych;
- zmianę capabilities, mountów, urządzeń albo trybu privileged;
- proces w kontenerze inicjujący połączenia do paneli węzła;
- rozbieżność między obrazem, podpisem i uruchomionym procesem;
- nowy Pod z host namespaces lub hostPath poza zatwierdzonym DaemonSetem.
Logi muszą opuścić węzeł, ponieważ po rzeczywistym escape lokalny napastnik może próbować je zmienić. Alert powinien zachować identyfikator kontenera, image digest, Pod, namespace, ServiceAccount, node i proces nadrzędny.
Hardening hosta i workloadu
- Aktualizuj kernel, runtime i orkiestrator według ryzyka oraz ekspozycji.
- Uruchamiaj proces jako nie-root i rozważ user namespaces lub rootless.
- Ustaw
no-new-privileges,drop ALLi dodawaj minimalne capabilities. - Zachowaj domyślny seccomp albo bardziej restrykcyjny profil przetestowany regresyjnie.
- Egzekwuj AppArmor/SELinux zamiast trybu complain/unconfined.
- Używaj read-only root filesystem i minimalnych zapisywalnych wolumenów.
- Blokuj privileged, host namespaces, hostPath, niezatwierdzone urządzenia i sockety runtime.
- Separuj wysoko uprzywilejowane DaemonSety na dedykowane węzły.
- Minimalizuj obraz i stosuj hardening Linuksa na hoście.
- Projektuj domyślne bezpieczne ustawienia zgodnie z Secure by Design.
W orkiestracji te ustawienia powinny być egzekwowane jako kod i sprawdzane razem z szerszym programem bezpieczeństwa Kubernetes i kontenerów, a nie pozostawione do decyzji autora pojedynczego manifestu.
Checklista odporności na container escape
- Każdy workload ma uzasadniony UID/GID i nie działa domyślnie jako root.
-
privileged, host namespaces, hostPath i urządzenia są zabronione domyślnie. - Capabilities zaczynają od
drop ALLi mają właściciela wyjątku. - Seccomp jest załadowany i zweryfikowany na działającym procesie.
- AppArmor/SELinux działa w trybie enforce na każdym węźle.
- Sockety runtime i kubelet nie są montowane do zwykłych workloadów.
- Kernel i runtime mają inwentaryzację wersji oraz SLA aktualizacji.
- Testy izolacji odbywają się na zgodnym laboratorium, nie przez exploit produkcji.
- SOC koreluje kernel, runtime, Pod, ServiceAccount i image digest.
- Retest obejmuje autoscaling, węzły zapasowe i ścieżki awaryjne aplikacji.


