Atak na kontenery w Kubernetes: jak błędne RBAC pozwoliło na eskalację uprawnień i kradzież danych

0
26
Rate this post

Nawigacja:

Kontekst incydentu: jak wyglądał atak na klaster Kubernetes

Atak dotknął typowe środowisko produkcyjne oparte na Kubernetes: kilka węzłów, kilkanaście namespace, mikrousługi HTTP i przetwarzanie danych wsadowych. Zespół DevOps korzystał z domyślnych wzorców konfiguracji i gotowych ról RBAC, aby szybko uruchamiać kolejne usługi.

Punktem startowym była zwykła aplikacja webowa wystawiona przez Ingress, działająca w osobnym namespace, ze standardowym service account przypisanym automatycznie przez Kubernetes. Sama aplikacja miała ograniczony dostęp do systemu plików i nie korzystała z hostPath ani trybu privileged. Na papierze wyglądało to poprawnie.

Kluczowy problem krył się w konfiguracji RBAC, która łączyła ten pozornie nieszkodliwy service account z szerokimi uprawnieniami na poziomie całego klastra. Atakujący, który przejął kontrolę nad tym jednym padem, był w stanie „przekuć” lokalne wejście w pełne przejęcie klastra i kradzież danych.

Wektor wejścia: pojedynczy kontener jako punkt zaczepienia

Wejście do klastra nastąpiło przez podatność w aplikacji HTTP: klasyczne RCE (remote code execution) w jednym z endpointów. Z perspektywy bezpieczeństwa sieci atak wyglądał jak zwykły ruch HTTP – brak skomplikowanych skanów, pojedyncze żądanie wykorzystujące błąd logiki.

Efektem tego błędu było uzyskanie powłoki w poadzie aplikacyjnym. Na pierwszy rzut oka niewiele: kontener działał jako nieuprzywilejowany użytkownik, bez dostępu do hosta. W normalnych warunkach taki dostęp powinien kończyć się na możliwościach typowego „pod usera”, ograniczonego do przestrzeni danego kontenera.

W praktyce każdy pod ma przypisany service account, a razem z nim token do komunikacji z Kubernetes API. Jeśli ten token ma szerokie uprawnienia, kontener przestaje być „tylko kontenerem” i zamienia się w wejście do całego klastra.

Rola RBAC w łańcuchu ataku

RBAC w Kubernetes decyduje, co może zrobić każdy pod, użytkownik czy proces odwołujący się do API serwera. W omawianym incydencie to nie jądro Linuksa ani konfiguracja sieci zawiodły, ale polityka przydziału ról.

Service account używany przez aplikację produkcyjną był powiązany z ClusterRoleBinding dającym dostęp typu „admin” do wielu zasobów klastra. Zostało to zrobione „tymczasowo”, aby szybciej rozwiązać problem z wdrożeniem. Nikt później nie wrócił do tej zmiany.

Na tym tle zwykły błąd w aplikacji stał się krytycznym incydentem: jeden poad, jedna podatność i jedno źle dobrane powiązanie roli wystarczyły, aby przejąć kontrolę nad konfiguracją i danymi klastra.

Konsekwencje incydentu dla klastra i danych

Konsekwencje były typowe dla nadużycia błędnej konfiguracji RBAC w Kubernetes, ale ich skala zaskoczyła zespół:

  • dostęp do secrets w wielu namespace, w tym haseł do baz danych i kluczy API,
  • możliwość tworzenia nowych podów z dowolnymi obrazami i parametrami bezpieczeństwa,
  • zdalne exec do innych podów, także tych z krytycznymi aplikacjami backendowymi,
  • odczyt konfiguracji backupów i dostęp do zasobów persistent volume,
  • instalacja backdoora w postaci ukrytego deploymentu w oddzielnym namespace.

Wyciek obejmował nie tylko to, co znajdowało się w samym klastrze. Po przejęciu secrets atakujący miał już klucze do zewnętrznych baz, brokerów komunikacyjnych czy usług chmurowych. Incydent z poziomu jednego kontenera szybko stał się problemem całej organizacji.

Podstawy Kubernetes i RBAC potrzebne do zrozumienia incydentu

Aby dobrze przeanalizować atak na kontenery w Kubernetes, trzeba jasno widzieć, jakie zasoby zostały wykorzystane oraz jak kubernetesowy RBAC łączy się z uprawnieniami service accounts.

Zasoby i obiekty, które wykorzystał atakujący

W trakcie incydentu kluczowe były konkretne typy obiektów Kubernetes, a nie ogólne pojęcia. Warto przejść przez nie po kolei:

  • Pod – podstawowa jednostka uruchomieniowa. To właśnie w poadzie aplikacyjnym atakujący uzyskał pierwszą powłokę. Ten poad miał zamontowany token service account.
  • Deployment – kontroler zarządzający replikami podów. Atakujący mógł modyfikować istniejące deploymenty lub tworzyć nowe, aby wprowadzić własne obrazy.
  • Namespace – logiczna przestrzeń izolacji zasobów. Błędne RBAC spowodowało, że service account z jednego namespace miał dostęp do wielu innych.
  • Service Account – tożsamość techniczna używana przez pody do rozmowy z API serwerem. Z nią powiązany był token, który posłużył do eskalacji uprawnień.
  • Secret – obiekt przechowujący poufne dane: hasła, tokeny, klucze. Odczyt secrets z wielu namespace stał się głównym celem atakującego.
  • ConfigMap – konfiguracja aplikacyjna. Nie zawsze zawiera „sekrety”, ale często dane, które pozwalają odtworzyć architekturę i lokalizację zasobów.

Każdy z tych obiektów był chroniony przez RBAC, ale ze względu na źle zaprojektowane role faktyczna ochrona była iluzoryczna.

Model RBAC: Role, ClusterRole, RoleBinding, ClusterRoleBinding

RBAC w Kubernetes opiera się na czterech głównych typach obiektów:

  • Role – definiuje uprawnienia w jednym namespace. Określa, jakie akcje można wykonywać na jakich zasobach, ale tylko w konkretnej przestrzeni nazw.
  • ClusterRole – może dotyczyć wszystkich namespace oraz zasobów klastrowych (np. nodes, persistentvolumes). Używana, gdy potrzebne są uprawnienia wykraczające poza pojedynczy namespace.
  • RoleBinding – przypisuje Role (lub ClusterRole) do użytkownika lub service account, ale zawsze w specificznym namespace.
  • ClusterRoleBinding – przypisuje ClusterRole do użytkownika lub service account w skali całego klastra.

Krytyczny błąd polegał na użyciu ClusterRoleBinding dla service account aplikacyjnego. W praktyce oznaczało to, że poad w jednym namespace miał uprawnienia, których zasięg sięgał daleko poza jego środowisko.

Zakres uprawnień: verbs i egzekwowanie przez kube-apiserver

Uprawnienia w RBAC definiuje się za pomocą tzw. verbs, czyli czasowników określających, jakie operacje mogą być wykonane na zasobach. Najczęściej spotykane to:

  • get – odczyt pojedynczego zasobu,
  • list – listowanie wielu zasobów danego typu,
  • watch – śledzenie zmian w czasie rzeczywistym,
  • create – tworzenie nowych obiektów,
  • update / patch – modyfikacja istniejących obiektów,
  • delete – usuwanie zasobów,
  • impersonate – działanie w imieniu innego użytkownika (szczególnie niebezpieczne),
  • pods/exec, pods/attach – wykonanie komendy w kontenerze, podłączenie się do strumieni.

Kube-apiserver przy każdym żądaniu API sprawdza, czy token (service account lub użytkownika) ma prawo wykonać daną operację na danym zasobie. Decyzja to proste „allow/deny” wynikające z dopasowania do istniejących ról i bindingów.

Jeżeli w konfiguracji pojawiają się wildcardy typu „verbs”: [„*”] lub „resources”: [„*”], kube-apiserver traci możliwość granularnego rozróżniania akcji. W omawianym ataku dokładnie taki rodzaj konfiguracji (szerokie „*”) sprawił, że API serwer zezwalał na prawie wszystko, co atakujący chciał zrobić.

Rekonstrukcja ataku krok po kroku – od pojedynczego kontenera do całego klastra

Atak na kontenery w Kubernetes rzadko jest jedną akcją. To ciąg logicznych kroków: wejście, rozpoznanie, eskalacja, ruch boczny i eksfiltracja danych. Poniższa rekonstrukcja pokazuje typowy scenariusz dla incydentu z błędnym RBAC.

Faza 1 – wejście do kontenera aplikacyjnego

Wejście nastąpiło przez lukę w kodzie aplikacji. Scenariusz był prosty: podatny endpoint pozwalał na wykonanie komendy systemowej na serwerze aplikacyjnym. W Kubernetes tym „serwerem” jest kontener wewnątrz poda.

Po udanym wykorzystaniu podatności atakujący uruchomił interaktywną powłokę. Mógł użyć np. prostego reverse shella lub wstrzyknięcia poleceń, aby uzyskać dostęp do /bin/sh wewnątrz kontenera.

W tym momencie miał:

  • dostęp do systemu plików kontenera,
  • możliwość uruchamiania procesów w jego przestrzeni,
  • lokalny wgląd w zmienne środowiskowe (a więc też potencjalne adresy API).

Na razie wszystko działo się wewnątrz jednego kontenera. To etap, na którym dobrze skonfigurowany RBAC, brak wrażliwych danych w zmiennych środowiskowych oraz ograniczenia sieciowe mogłyby poważnie ograniczyć możliwości atakującego.

Faza 2 – rozpoznanie uprawnień w klastrze

Kolejnym krokiem było sprawdzenie, czy kontener ma dostęp do Kubernetes API. Atakujący zajrzał do typowej ścieżki, w której Kubernetes trzyma tokeny:

  • /var/run/secrets/kubernetes.io/serviceaccount/token
  • /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
  • /var/run/secrets/kubernetes.io/serviceaccount/namespace

Obecność tych plików oznacza, że poad używa domyślnego lub dedykowanego service account. Token jest krótkim JWT, który może być używany jako nagłówek Bearer w żądaniach HTTP do kube-apiservera.

Atakujący wykonał następne kroki:

  • odczytał $KUBERNETES_SERVICE_HOST i $KUBERNETES_SERVICE_PORT ze zmiennych środowiskowych,
  • potwierdził, że API serwer jest dostępny na adresie https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT,
  • wykonał żądanie typu curl -k -H "Authorization: Bearer <token>" https://.../api, aby sprawdzić, czy token jest akceptowany.

Po potwierdzeniu dostępu rozpoczęło się systematyczne rozpoznanie:

  • GET /api/v1/namespaces – sprawdzenie dostępnych namespace,
  • GET /api/v1/pods lub /api/v1/namespaces/<ns>/pods – sprawdzenie, jakie pody są widoczne,
  • GET /api/v1/secrets – test, czy RBAC pozwala na odczyt secrets.

Jeśli RBAC działa poprawnie, wiele z tych żądań powinno zakończyć się błędem 403. W tym przypadku większość zwracała 200, co wyraźnie pokazywało, że service account ma zdecydowanie za dużo uprawnień.

Faza 3 – eskalacja i lateral movement w klastrze

Po rozpoznaniu atakujący wiedział, że ma dostęp do szerokiego zestawu zasobów. Kluczowe było stwierdzenie, że token pozwala na:

  • list i get secrets w wielu namespace,
  • create pody w wybranych namespace,
  • wykonywanie exec na istniejących podach.

To otworzyło dwie drogi eskalacji:

  1. Stworzenie nowego poda z obrazem kontrolowanym przez atakującego, zawierającego narzędzia ofensywne (np. skanery, archiwizatory, skrypty do exfiltracji).
  2. Podłączenie się do już działających podów z bardziej uprzywilejowanymi uprawnieniami (np. tych, które mają zamontowane persistent volumes lub działają z uprawnieniem do node’a).

W praktyce wykorzystał oba podejścia. Najpierw użył create pod w namespace, w którym RBAC był najmniej restrykcyjny. Utworzył poad z obrazem zawierającym narzędzia sieciowe i skrypty do masowego odczytu secrets. Następnie, korzystając z pods/exec, rozprzestrzenił się do kolejnych krytycznych podów, zdobywając dodatkowe wektory dostępu do danych.

Faza 4 – kradzież danych i utrwalenie dostępu

Gdy atakujący upewnił się, że ma stabilne uprawnienia i kilka punktów obecności, przeszedł do eksfiltracji danych. W pierwszej kolejności zebrał:

  • secrets z namespace produkcyjnych: hasła do baz danych, klucze API, certyfikaty,
  • ConfigMapy z konfiguracjami aplikacji i adresami end-pointów zewnętrznych systemów,
  • informacje o persistent volumes i backupach.

Dane były pakowane i wysyłane kanałami wychodzącymi, które i tak były używane przez aplikacje (np. przez zwykłe połączenia HTTPS do rzekomo „zaufanych” domen). Dla systemów monitoringu ruch wydawał się typowy.

Równolegle atakujący utrwalił dostęp:

  • utworzył osobny namespace z nazwą przypominającą element systemu monitoringu,
  • uruchomił tam deployment z niewinnym obrazem, ale z dodatkowym kontenerem sidecar jako backdoor,
  • Faza 5 – próba ukrycia śladów i obejścia monitoringu

    Po zbudowaniu przyczółków atakujący ograniczył ilość generowanego szumu. Ograniczył skanowanie API, przechodząc na okresowe, małe żądania, wplatając je w regularny ruch aplikacji.

    Na poziomie klastra podjął kilka prostych działań utrudniających analizę:

  • zmniejszył poziom logowania w wybranych komponentach aplikacyjnych poprzez modyfikację ConfigMap,
  • usunął własne pody „narzędziowe” po zakończeniu jednorazowych działań,
  • czyścił dane tymczasowe w kontenerach, które wykorzystywał tylko raz.

Nie ingerował w logi kube-apiservera ani audit logi, bo nie miał do nich bezpośredniego dostępu, ale liczył na ich niski poziom szczegółowości i zbyt krótki retention.

Zbliżenie ekranu komputera z interfejsem cyberbezpieczeństwa w zielonych barwach
Źródło: Pexels | Autor: Tima Miroshnichenko

Główne błędy w konfiguracji RBAC, które umożliwiły atak

Analiza konfiguracji pokazała kilka konkretnych decyzji, które razem złożyły się na pełną eskalację uprawnień. Każda z nich osobno byłaby mniej groźna, dopiero ich kombinacja otworzyła drzwi na oścież.

Zbyt szeroka ClusterRole przypisana do service account aplikacji

Kluczowy był sam fakt użycia ClusterRole dla zwykłej aplikacji biznesowej. Definicja roli wyglądała schematycznie:

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: app-global-access
rules:
- apiGroups: [""]
  resources: ["pods", "secrets", "configmaps"]
  verbs: ["get", "list", "watch", "create", "update", "delete"]
- apiGroups: ["apps"]
  resources: ["deployments", "replicasets"]
  verbs: ["get", "list", "watch"]

Role miała wykraczające poza potrzeby aplikacji uprawnienia do secrets oraz możliwość ich modyfikacji. Dawało to de facto kontrolę nad konfiguracją innych komponentów.

ClusterRoleBinding z wildcardem na service account

Następny błąd to sposób powiązania powyższej roli:

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: app-global-access-binding
subjects:
- kind: ServiceAccount
  name: default
  namespace: app-prod
roleRef:
  kind: ClusterRole
  name: app-global-access
  apiGroup: rbac.authorization.k8s.io

Service account default w namespace produkcyjnym dostał w ten sposób globalne uprawnienia. Każdy poad, który z jakiegokolwiek powodu używał domyślnego service account, dziedziczył ten zestaw uprawnień.

Wystarczył więc exploit w jednej aplikacji, żeby w praktyce uzyskać dostęp administracyjny do wielu zasobów w całym klastrze.

Wildcardy w verbs i resources

W niektórych środowiskach znaleziono jeszcze bardziej ryzykowną wersję roli, z użyciem pełnych wildcardów:

rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["*"]

Taka konfiguracja usuwa wszelkie granice. Każda nowa funkcja w klastrze automatycznie staje się dostępna dla posiadacza tej roli. To szczególnie groźne przy aktualizacjach klastrów lub dodawaniu nowych CRD.

Brak separacji obowiązków między namespace

Namespaces były używane głównie do porządkowania aplikacji, nie jako granica bezpieczeństwa. Te same role były stosowane do namespace deweloperskich i produkcyjnych.

W praktyce service account z jednego namespace mógł:

  • listować i odczytywać secrets w innym namespace,
  • tworzyć tam pody,
  • modyfikować ConfigMapy używane przez systemy krytyczne.

Jakikolwiek dostęp do środowiska „mniej ważnego” (np. test, staging) automatycznie umożliwiał przeskok do produkcji.

Niedocenienie uprawnienia pods/exec

W kilku rolach zastosowano zbyt liberalne uprawnienia do podów:

rules:
- apiGroups: [""]
  resources: ["pods", "pods/exec"]
  verbs: ["get", "list", "watch", "create", "delete", "patch"]

Uprawnienie pods/exec zostało dodane jako „wygodne do debugowania”. W konsekwencji osoba (lub process) z tą rolą mogła wejść do dowolnego kontenera w widocznym zakresie namespace.

Dla atakującego oznaczało to możliwość wykonywania komend w podach, do których nie miałby dostępu sieciowego ani systemowego z zewnątrz.

Dziedziczenie uprawnień przez domyślne service account

W kilku namespace nie wyłączono automatycznego montowania tokenów service account do podów:

automountServiceAccountToken: true

W połączeniu z użyciem default SA sprawiło to, że każdy nowy poad dostawał token z szerokimi uprawnieniami. Nawet krótkotrwałe joby czy zadania batchowe były potencjalnym wektorem ataku.

Co faktycznie mógł zrobić atakujący dzięki błędnemu RBAC

Uprawnienia nadane aplikacyjnemu service account przekładały się na bardzo konkretne możliwości. Da się je podzielić na kilka kategorii: dostęp do danych, kontrola nad infrastrukturą aplikacyjną oraz budowanie trwałości.

Dostęp i eksfiltracja wrażliwych danych

Najpierw atakujący wykorzystał możliwość list/get secrets i ConfigMap:

  • odczytał dane uwierzytelniające do baz danych kilku usług,
  • pozyskał tokeny i klucze API do systemów zewnętrznych (płatności, CRM, systemy analityczne),
  • zebrał certyfikaty i klucze prywatne używane przez aplikacje.

Te informacje można było wykorzystać poza klastrem: do logowania się bezpośrednio do baz, API partnerów czy paneli administracyjnych.

Kontrola nad workloadami i zmianami w konfiguracji

Dzięki możliwości create/update/delete na wybranych zasobach atakujący mógł sterować zachowaniem aplikacji:

  • modyfikował Deploymenty, dodając sidecary lub zmieniając obrazy na własne,
  • tworzył nowe pody typu „narzędziowego” w produkcyjnych namespace,
  • aktualizował ConfigMapy, by przekierować ruch na własne endpointy (np. wstrzyknięcie dodatkowych nagłówków, zmiana adresu usługi logowania).

To podejście jest trudniejsze do szybkiego wykrycia, bo zmiany wyglądają jak zwykła działalność operacyjna – nowy release, poprawka konfiguracyjna, dodatkowy komponent.

Nieautoryzowany dostęp do innych kontenerów

Uprawnienie pods/exec otworzyło dostęp interaktywny do wielu kontenerów:

  • wchodził do podów systemów raportowych, które miały szerokie uprawnienia do baz danych,
  • dostawał się do kontenerów narzędzi backupowych z dostępem do pełnych snapshotów,
  • sprawdzał pliki konfiguracyjne w podach z integracjami legacy, gdzie często trzymano poświadczenia „na twardo”.

Takie wykorzystanie exec pozwalało ominąć tradycyjne bariery sieciowe i firewall między serwisami. Działał „od środka”, tak jakby był procesem należącym do aplikacji.

Tworzenie i utrzymanie backdoorów

Atakujący mógł także budować trwałe punkty wejścia do klastra, niezależne od pierwotnej podatności w aplikacji:

  • utworzył dedykowany service account z nową rolą i bindingiem,
  • dodał Deployment w mało monitorowanym namespace (np. „tools” lub „metrics”),
  • skonfigurował w nim kontener, który cyklicznie łączył się z zewnętrznym serwerem C2 przez HTTPS.

W razie załatania podatności w aplikacji źródłowej, dostęp przez ten backdoor pozostawał aktywny – do czasu całkowitej rewizji RBAC i zasobów w klastrze.

Potencjalny wpływ na bezpieczeństwo węzłów (nodes)

W badanym incydencie uprawnienia nie obejmowały bezpośredniego zarządzania node’ami, ale konfiguracja była blisko tej granicy. Wystarczyłoby dodanie do ClusterRole:

- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "list", "patch"]

Taki zakres mógłby umożliwić np. modyfikację etykiet node’ów, wpływ na schedulowanie lub manipulację taintami. W skrajnych przypadkach – przy błędach w innych komponentach – doprowadziłoby to do eskalacji poza warstwę podów.

Sygnatury i ślady ataku w logach – jak rozpoznać podobne zdarzenie

Nawet przy słabszej obserwowalności większość kroków atakującego zostawia ślady. Problem w tym, że sygnały są rozproszone: w kube-apiserver, audit logach, logach aplikacji i agentach bezpieczeństwa.

Nietypowe wzorce wywołań Kubernetes API

Najbardziej charakterystyczne są nagłe, szerokie zapytania do API z jednego podmiotu (user/service account). Typowe oznaki:

  • wzrost liczby żądań list i get na secrets i configmaps w krótkim czasie,
  • zapytania do wielu namespace z tego samego tokena, choć normalnie aplikacja korzysta tylko z jednego,
  • iteracyjne przechodzenie po API: najpierw /api, później /api/v1/namespaces, potem /pods i /secrets.

W audit logach można to wychwycić, budując prostą korelację: „service account X odczytał więcej niż N secrets w czasie T” albo „service account X odwołuje się do namespace, w których nie występują jego pody”.

Ślady użycia pods/exec i port-forward

Wywołania exec i port-forward są dobrym wskaźnikiem podejrzanej aktywności, jeśli występują w kontekście kont produkcyjnych:

  • w audit logach pojawiają się żądania z verb: create, resource: pods/exec,
  • widać nietypowe kombinacje: np. service account aplikacji biznesowej wykonuje exec w podzie systemu backupowego,
  • zwiększa się liczba krótkotrwałych połączeń websocket/upgrade z API serwerem (kanały exec).

W praktyce w wielu środowiskach użycie pods/exec nie jest aktywnie monitorowane, przez co atak może długo pozostać niewykryty.

Tworzenie i kasowanie podów o podejrzanych nazwach

Atakujący zwykle nie poświęca czasu na idealne dopasowanie się do konwencji nazewniczych. Typowe oznaki:

  • pojawienie się pojedynczych podów, jobów lub deploymentów o ogólnych nazwach: tool, debug, test, helper,
  • tworzenie zasobów w namespace, w których zmiany są rzadkie (np. systemy analityczne, raportowe),
  • krótkie życie poda: powstaje, wykonuje serię intensywnych operacji na API lub sieci i znika.

Podczas incydentu w logach kontrolera widoczne były cykle tworzenia i kasowania pojedynczych podów, odpalanych z tej samej ClusterRoleBinding.

Nieoczekiwane modyfikacje ConfigMap i Secret

Zmiany w ConfigMapach i Secrets także zostawiają czytelny ślad:

  • częstsze niż zwykle operacje update i patch na zasobach konfiguracyjnych,
  • modyfikacje wykonywane przez service account, który normalnie tylko odczytuje te zasoby,
  • tworzenie nowych Secrets z nietypowymi nazwami (np. zawierającymi „test”, „tmp”, „backup”).

Diff zawartości ConfigMap często wskazuje bezpośrednio na charakter ataku: dodatkowe endpointy, klucze, zmienione adresy usług.

Niespójność między ruchem sieciowym a profilem aplikacji

Choć atakujący starał się ukryć w regularnym ruchu HTTPS, dokładniejsza analiza mogła wykryć anomalie:

  • nowe domeny docelowe pojawiające się w ruchu wychodzącym z podów, które wcześniej kontaktowały się tylko z ograniczonym zestawem usług,
  • wzrost ilości danych wysyłanych na zewnątrz w godzinach niskiej aktywności użytkowników,
  • połączenia z adresami, które nie są zdefiniowane w oficjalnej konfiguracji (brak ich w repozytorium IaC lub Helm chartach).

Narzędzia monitorujące egress z klastra, powiązane z tożsamością poda i namespace, pozwalają skorelować te anomalie z konkretnymi pods/exec i odczytem secrets.

Zachowanie kontenerów wskazujące na obecność powłoki

W logach aplikacji często widać symptomy ręcznej ingerencji:

  • nietypowe komendy wpisywane w powłoce (np. env, ls /, kubectl jeśli dostępne),
  • nagłe pojawienie się nowych procesów powłoki (/bin/sh, /bin/bash) w metrykach zebranych przez agenta,
  • zmiany w użyciu CPU i pamięci bez odpowiadającego im ruchu użytkowników.

Nawet jeśli atakujący stara się używać tylko curl i prostych narzędzi, fakt wejścia w interaktywną powłokę zwykle da się wykryć, jeśli zbierane są podstawowe metryki procesów.

Najczęściej zadawane pytania (FAQ)

Jak błąd w RBAC może zamienić pojedynczy kontener w pełne przejęcie klastra Kubernetes?

Dzieje się tak, gdy service account używany przez aplikację ma powiązane zbyt szerokie uprawnienia, np. przez ClusterRoleBinding z rolą typu admin obejmującą cały klaster. Wtedy każdy, kto przejmie ten jeden pod (np. przez RCE w aplikacji), automatycznie zyskuje te same uprawnienia wobec Kubernetes API.

Z perspektywy jądra Linuksa kontener może być poprawnie odizolowany i nieuprzywilejowany, ale token service account montowany do poda otwiera furtkę do API serwera. Jeśli role zawierają wildcardy typu "verbs": ["*"], "resources": ["*"], atakujący może tworzyć, modyfikować i usuwać zasoby w całym klastrze, odczytywać secrets i wykonywać exec do innych podów.

Jakie były kluczowe kroki ataku na kontenery w opisanym incydencie?

Scenariusz miał kilka prostych, ale krytycznych etapów. Najpierw atakujący wykorzystał podatność RCE w aplikacji HTTP wystawionej przez Ingress i uzyskał powłokę w jednym podzie. Kontener działał jako nieuprzywilejowany użytkownik, ale miał zamontowany token service account.

Kolejny krok to użycie tego tokena do komunikacji z Kubernetes API i sprawdzenie, jakie ma uprawnienia. Ponieważ service account był związany z szeroką ClusterRole przez ClusterRoleBinding, atakujący mógł m.in. listować i odczytywać secrets w wielu namespace, tworzyć własne deploymenty (np. backdoor), wykonywać zdalne exec do innych podów i odczytać konfigurację backupów oraz persistent volumes.

Jakie konkretne zasoby Kubernetes zostały wykorzystane do eskalacji uprawnień i kradzieży danych?

Atak koncentrował się na kilku podstawowych typach obiektów Kubernetes, które większość klastrów ma skonfigurowane podobnie. Kluczowe były:

  • Pod – pierwszy punkt wejścia, miejsce uzyskania powłoki i tokenu service account.
  • Deployment – służył do tworzenia nowych podów z obrazami atakującego i innymi parametrami bezpieczeństwa.
  • Namespace – dzięki błędnemu RBAC jeden service account miał dostęp do wielu przestrzeni nazw.
  • ServiceAccount – techniczna tożsamość z przypisanymi nadmiernymi rolami.
  • Secret – główny cel: hasła do baz, klucze API, dane do usług chmurowych.
  • ConfigMap – źródło informacji o architekturze i lokalizacji zewnętrznych zasobów.

Jakie były praktyczne konsekwencje błędnej konfiguracji RBAC dla bezpieczeństwa danych?

Bezpośrednio w klastrze atakujący uzyskał dostęp do secrets w wielu namespace, mógł tworzyć i modyfikować deploymenty, wykonywać exec do innych podów i wprowadzić trwały backdoor jako ukryty deployment. Obejmowało to również aplikacje backendowe uznawane za „krytyczne”.

Pośrednio skutki były jeszcze poważniejsze. Dane z secrets dały mu dostęp do zewnętrznych baz danych, brokerów komunikacyjnych czy usług chmurowych, więc incydent przestał być problemem jednego klastra. Z jednego kontenera zrobił się realny wyciek danych całej organizacji, w tym kopii zapasowych i systemów integracyjnych.

Jak sprawdzić, czy mój service account w Kubernetes ma zbyt szerokie uprawnienia?

Na początek warto przejrzeć istniejące RoleBinding i ClusterRoleBinding. Szukaj powiązań, w których dany service account jest przypięty do ClusterRole z wildcardami resources: ["*"] lub verbs: ["*"], albo do ról typu cluster-admin używanych w środowisku produkcyjnym.

W praktyce przydają się polecenia takie jak kubectl get clusterrolebinding -o yaml oraz kubectl get rolebinding -A -o yaml i filtrowanie po nazwie service account. Dodatkowo można użyć kubectl auth can-i z tokenem danego service account, aby sprawdzić, jakie operacje są faktycznie dozwolone na wybranych zasobach.

Jak zabezpieczyć klaster Kubernetes przed podobnym atakiem przez kontener?

Podstawą jest ograniczenie uprawnień RBAC i service accounts do minimalnie potrzebnych. Zamiast przypinać aplikacje do ogólnych ClusterRole z pełnymi prawami, lepiej tworzyć dedykowane Role per namespace, z jasno określonymi verbs i zasobami, bez wildcardów. Dodatkowo warto rozdzielić konta techniczne dla różnych klas aplikacji (frontend, backend, systemowe).

Drugi filar to higiena aplikacji i środowiska: testy bezpieczeństwa kodu (żeby ograniczyć RCE), ograniczenia securityContext (brak privileged, brak hostPath, brak capability ponad konieczne), monitoring wywołań do kube-apiserver i alerty na nietypowe operacje, takie jak masowe odczyty secrets czy nagłe tworzenie nowych deploymentów w niespodziewanych namespace.

Czym różnią się Role, ClusterRole, RoleBinding i ClusterRoleBinding w kontekście bezpieczeństwa?

Różnica dotyczy przede wszystkim zasięgu uprawnień. Role działa tylko w jednym namespace, więc nawet pomyłka ogranicza szkody do tej przestrzeni. ClusterRole może obejmować wszystkie namespace oraz zasoby klastrowe, jak nodes czy persistent volumes.

RoleBinding przypisuje Role lub ClusterRole do podmiotów (użytkowników, service accounts) w konkretnym namespace. ClusterRoleBinding wiąże ClusterRole globalnie, w skali całego klastra. W opisanym incydencie to właśnie błędne użycie ClusterRoleBinding dla zwykłego service account aplikacyjnego sprawiło, że pojedynczy pod miał niemal nieograniczone możliwości w całej infrastrukturze.

Bibliografia

  • Kubernetes Documentation – Authorization Overview (RBAC). Cloud Native Computing Foundation – Oficjalny opis modelu autoryzacji i RBAC w Kubernetes
  • Kubernetes Documentation – Service Accounts. Cloud Native Computing Foundation – Jak działają service accounts, tokeny i ich użycie przez pody
  • Kubernetes Security – Best Practices for RBAC. Google Cloud – Rekomendacje ograniczania uprawnień, unikania wildcardów w RBAC
  • Kubernetes Hardening Guide. National Security Agency (2022) – Zalecenia bezpieczeństwa dot. RBAC, service accounts i dostępu do API