Blog

Obrona przed ucieczką z kontenera: Praktyczny przewodnik po izolacji Dockera dla hostingu wielodostępnego

Dowiedz się, jak zabezpieczyć kontenery Dockera przed lukami umożliwiającymi ucieczkę i błędami izolacji w środowiskach wielodostępnych, korzystając z konkretnych kroków i przykładów.

Podsumowanie

Kontenery Dockera współdzielą jądro hosta, co czyni izolację krytyczną – zwłaszcza w hostingu wielodostępnym, gdzie jedna ucieczka z kontenera może zagrozić wszystkim najemcom. Wielu deweloperów zakłada, że kontenery to doskonale odizolowane maszyny wirtualne, ale rzeczywistość jest inna. Ten artykuł wyjaśnia funkcje jądra Linuksa stojące za izolacją Dockera (przestrzenie nazw, cgroups) oraz wektory ataków, które je zagrażają. Dowiesz się, jakie praktyczne kroki podjąć, aby wzmocnić konfigurację Dockera: ograniczyć uprawnienia, używać bezpiecznego środowiska uruchomieniowego, skanować obrazy i wdrażać segmentację sieci. Korzystając z przykładu dostawcy hostingu WordPress wielodostępnego, zobaczysz, jak zastosować te zabezpieczenia. Omówimy również zastrzeżenia, takie jak kompromisy w zakresie wydajności i użycie seccomp/AppArmor. Celem jest zapewnienie solidnej strategii izolacji, która zapobiegnie ucieczkom z kontenerów i zapewni bezpieczeństwo najemcom.

Wprowadzenie

Jeśli prowadzisz platformę hostingową wielodostępną – czy to współdzielony hosting WordPress, aplikację SaaS, czy usługę środowiska deweloperskiego – ucieczka z kontenera jest koszmarem. Luka w jądrze lub błąd konfiguracji może pozwolić jednemu najemcy na wydostanie się z kontenera i uzyskanie dostępu do danych innych najemców lub samego hosta. Izolacja Dockera opiera się na funkcjach jądra Linuksa, takich jak przestrzenie nazw i cgroups, ale domyślne konfiguracje często są niewystarczające dla solidnego bezpieczeństwa. Ten artykuł przeprowadzi Cię przez wektory ataków i dostarczy praktycznych kroków do zabezpieczenia kontenerów Dockera, zilustrowanych przykładem hostingu WordPress wielodostępnego. Aby uzyskać szerszy przegląd orkiestracji produkcyjnej, zapoznaj się z naszym przewodnikiem po Orkiestracji gotowych do produkcji aplikacji kontenerowych.

Zrozumienie izolacji Dockera

Kontenery Dockera wykorzystują przestrzenie nazw Linuksa do zapewnienia izolacji na poziomie procesów: przestrzenie nazw PID izolują drzewa procesów, przestrzenie nazw sieci oddzielają interfejsy sieciowe, przestrzenie nazw montowania izolują punkty montowania systemu plików, a przestrzenie nazw użytkowników pozwalają na mapowanie roota kontenera na nieuprzywilejowanego użytkownika hosta. Grupy kontrolne (cgroups) ograniczają wykorzystanie zasobów, takich jak CPU, pamięć i I/O dysku. Te funkcje razem tworzą „piaskownicę” wokół każdego kontenera. Jednak w przeciwieństwie do maszyny wirtualnej, która uruchamia oddzielne jądro, kontenery współdzielą jądro hosta. Oznacza to, że luka w jądrze (np. CVE-2022-0492) może zostać wykorzystana do ucieczki z izolacji przestrzeni nazw kontenera. Dodatkowo, błędy konfiguracji, takie jak uruchamianie kontenerów jako root wewnątrz kontenera, nadawanie kontenerowi wszystkich uprawnień lub nieusuwanie niepotrzebnych uprawnień Linuksa, mogą poszerzyć powierzchnię ataku.

Wektory ataków

Typowe wektory ataków obejmują:

  • Eksploitacje jądra: Wykorzystanie błędu w jądrze hosta w celu uzyskania dostępu do hosta.
  • Kontenery uprzywilejowane: Uruchamianie z opcją --privileged nadaje wszystkie uprawnienia i omija większość izolacji.
  • Nadużycie uprawnień: Nawet bez pełnego trybu uprzywilejowanego, kontener z niebezpiecznymi uprawnieniami, takimi jak CAP_SYS_ADMIN lub CAP_NET_ADMIN, może montować systemy plików lub manipulować ustawieniami sieci.
  • Niebezpieczne praktyki dotyczące obrazów: Używanie obrazów bazowych ze znanymi lukami lub zawierających niepotrzebne narzędzia, takie jak kompilatory czy interpretery powłoki.
  • Współdzielone przestrzenie nazw montowania: Montowanie katalogów hosta w kontenerach może pozwolić na ucieczkę, jeśli nie jest tylko do odczytu.

Praktyczne kroki bezpieczeństwa

1. Uruchamiaj kontenery jako użytkownik niebędący rootem

Domyślnie Docker uruchamia kontenery jako root wewnątrz kontenera. Jeśli atakujący uzyska dostęp roota wewnątrz kontenera, ma większe możliwości. Utwórz użytkownika w swoim Dockerfile i użyj dyrektywy USER. Unikaj również używania flagi --user w Docker Compose do mapowania na dowolnego użytkownika hosta, jeśli to możliwe.

2. Odrzuć wszystkie uprawnienia i dodaj tylko potrzebne

Uprawnienia Linuksa dzielą uprawnienia superużytkownika na mniejsze jednostki. W Docker Compose użyj cap_drop: ALL, a następnie cap_add tylko wymagane (np. NET_BIND_SERVICE). Unikaj niebezpiecznych uprawnień, takich jak SYS_ADMIN, NET_ADMIN, SYS_PTRACE.

3. Używaj systemu plików roota tylko do odczytu

Ustaw read_only: true w definicji swojego kontenera. Zapobiega to zapisywaniu przez atakujących w systemie plików kontenera. Jeśli Twoja aplikacja potrzebuje zapisywać pliki tymczasowe, zamontuj tam wolumin tmpfs.

4. Włącz ponowne mapowanie przestrzeni nazw użytkowników

Ponowne mapowanie przestrzeni nazw użytkowników mapuje użytkownika roota kontenera na nieuprzywilejowanego użytkownika hosta. Dodaje to warstwę izolacji, ponieważ nawet jeśli root kontenera ucieknie, będzie miał uprawnienia zamapowanego użytkownika. Włącz to w /etc/docker/daemon.json za pomocą "userns-remap": "default". Pamiętaj, że może to skomplikować uprawnienia woluminów. Więcej informacji znajdziesz w Mastering Docker Isolation for Secure and Efficient Web Hosting.

5. Stosuj profile Seccomp i AppArmor/AppArmor

Seccomp ogranicza wywołania systemowe, które może wykonać kontener. Docker zapewnia domyślny profil seccomp, który blokuje niebezpieczne wywołania systemowe. Możesz również tworzyć niestandardowe profile. Podobnie, AppArmor (lub SELinux) zapewnia obowiązkową kontrolę dostępu. Użyj AppArmor, aby ograniczyć kontener do minimalnego zestawu dozwolonych operacji. Profil bezpieczeństwa można ustawić za pomocą security_opt w Docker Compose.

6. Używaj minimalnych obrazów bazowych i skanuj pod kątem luk

Wybieraj małe obrazy, takie jak Alpine lub Distroless, które mają mniejszą powierzchnię ataku. Regularnie skanuj obrazy za pomocą narzędzi takich jak Docker Scout, Trivy lub Clair. Zintegruj skanowanie z potokiem CI/CD, aby zapobiec wdrażaniu podatnych na ataki obrazów.

7. Segmentacja sieci za pomocą niestandardowych sieci mostkowych

Twórz oddzielne sieci mostkowe dla każdego najemcy lub warstwy aplikacji. Ogranicza to ruch wschód-zachód. W Docker Compose definiuj sieci i izoluj usługi. Użyj internal: true, jeśli usługa nie potrzebuje dostępu do Internetu. Reguły zapory sieciowej na hoście dodatkowo ograniczają ruch między kontenerami.

8. Ogranicz zasoby za pomocą cgroups

Ustaw limity CPU i pamięci w Docker Compose za pomocą deploy.resources.limits. Zapobiega to atakowi wyczerpania zasobów przez skompromitowany kontener. Dodatkowo ustaw kernel_memory i memory_reservation dla dokładniejszej kontroli.

Przykład z życia wzięty: Hosting WordPress wielodostępny z Docker Compose

Rozważ scenariusz, w którym hostujesz wiele witryn WordPress dla różnych klientów, każdą we własnym kontenerze Dockera. Niebezpieczna konfiguracja może wyglądać tak:

version: '3'
services:
  wordpress:
    image: wordpress:latest
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb
    volumes:
      - ./wp-content:/var/www/html/wp-content
  db:
    image: mysql:5.7
    environment:
      MYSQL_DATABASE: exampledb
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
      MYSQL_ROOT_PASSWORD: somewordpress
    volumes:
      - db_data:/var/lib/mysql
volumes:
  db_data:

Ta konfiguracja jest podatna: kontener WordPress działa jako root wewnątrz, ma wszystkie uprawnienia (ponieważ żadne nie zostały odrzucone), montuje katalog hosta z dostępem do zapisu i ma nieograniczony dostęp do sieci.

Teraz ją wzmocnijmy:

version: '3'
services:
  wordpress:
    image: wordpress:latest
    user: www-data
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    read_only: true
    tmpfs:
      - /var/www/html/wp-content/plugins
    security_opt:
      - seccomp=seccomp-profile.json
      - apparmor=wordpress-profile
    networks:
      - frontend
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb
    volumes:
      - wp-uploads:/var/www/html/wp-content/uploads
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
  db:
    image: mysql:5.7
    user: mysql
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    networks:
      - backend
    environment:
      MYSQL_DATABASE: exampledb
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
      MYSQL_ROOT_PASSWORD: somewordpress
    volumes:
      - db_data:/var/lib/mysql
    deploy:
      resources:
        limits:
          cpus: '0.25'
          memory: 128M
networks:
  frontend:
    driver: bridge
    internal: false
  backend:
    driver: bridge
    internal: true
volumes:
  wp-uploads:
  db_data:

Kluczowe ulepszenia:

  • Oba kontenery działają jako użytkownicy niebędący rootem (www-data i mysql).
  • Wszystkie uprawnienia odrzucone, dodano tylko NET_BIND_SERVICE.
  • System plików WordPress jest tylko do odczytu, z wyjątkiem montowania tmpfs i woluminu uploads.
  • Zastosowano profile seccomp i AppArmor (należałoby dostarczyć niestandardowe profile).
  • Oddzielne sieci izolują warstwę web od bazy danych, z siecią bazy danych jako wewnętrzną.
  • Limity zasobów zapobiegają wyczerpaniu zasobów.

Więcej informacji na temat utwardzania Dockera specyficznego dla WordPressa znajdziesz w Docker for WordPress: Why Isolated Containers Change Everything.

Zastrzeżenia

  • Ponowne mapowanie przestrzeni nazw użytkowników: Chociaż jest to potężne, przerywa montowanie woluminów, ponieważ zamapowany UID hosta nie jest tym samym co UID kontenera. Może być konieczne wcześniejsze utworzenie katalogów z poprawnymi uprawnieniami lub użycie woluminów Dockera z obsługą ponownego mapowania.
  • Profile Seccomp/AppArmor: Niestandardowe profile wymagają zrozumienia wzorców wywołań systemowych i dostępu do plików Twojej aplikacji. Zbyt restrykcyjne profile mogą zakłócić działanie. Testuj dokładnie.
  • Wydajność: Dodatkowe warstwy bezpieczeństwa, takie jak seccomp i AppArmor, mają minimalny narzut, ale limity zasobów i systemy plików tylko do odczytu mogą wpłynąć na aplikacje intensywnie zapisujące dane.
  • Złożoność orkiestracji: W środowisku wielodostępnym zarządzanie plikami Docker Compose dla każdego najemcy może stać się uciążliwe. Rozważ użycie narzędzia orkiestracyjnego wyższego poziomu, takiego jak Kubernetes, ale to wprowadza własne kwestie bezpieczeństwa.

Wniosek

Ucieczka z kontenera jest realnym zagrożeniem w hostingu wielodostępnym Dockera, ale można jej zapobiec. Rozumiejąc mechanizmy izolacji i stosując obronę w głąb – odrzucając uprawnienia, uruchamiając jako użytkownik niebędący rootem, włączając przestrzenie nazw użytkowników, seccomp, AppArmor, segmentację sieci i regularne skanowanie obrazów – możesz znacznie zmniejszyć ryzyko. Pamiętaj, że domyślne ustawienia Dockera nie są gotowe do produkcji dla obciążeń wielodostępnych. Wdróż te kroki już dziś, aby chronić swoich najemców i swoją infrastrukturę. Kompleksowy przegląd najlepszych praktyk bezpieczeństwa Dockera znajdziesz w Securing Your Web Applications with Docker: A Practical Guide to Isolation and Best Practices.

Sources (5)