İçeriğe geç
Sedat Özdemir
Yazılar

container-security

Docker Soketinden Root Shell'e: Konteyner İzolasyonu Bir İllüzyon mu?

Konteyner dünyasında izolasyonun bittiği ve 'escape' senaryolarının başladığı o ince çizgiyi teknik detaylarıyla inceliyoruz.

Sedat Özdemir
· 3 dk okuma

docker run --privileged --net=host --pid=host -it alpine /bin/sh komutunu çalıştırdığın an, o konteyner artık bir izolasyon katmanı değil, ana işletim sisteminin (host) kalbine giden açık bir otobandır. Eğer Red Team operasyonunda bir sızma gerçekleştirdiysem ve içeride bu parametrelerle çalışan bir konteyner bulduysam, kahvemi yudumlamaya başlayabilirim çünkü makinenin kontrolü artık bendedir.

İzolasyonun Sahte Güvenliği

Çoğu developer dostum konteynerları 'hafif siklet sanal makineler' sanıyor. Oysa konteyner dediğin şey, Linux kernel özelliklerinden (namespaces ve cgroups) başka bir şey değil. Eğer bu özellikleri doğru yapılandırmazsan, o 'güvenli' sandığın kutu aslında sadece bir kağıttan kaplandır.

Bir Red Teamer olarak ilk baktığım yer /var/run/docker.sock dosyasının konteyner içine mount edilip edilmediğidir. Genellikle monitoring araçları veya CI/CD agent'ları için yapılan bu hata, saldırgana Docker API'si üzerinde tam yetki verir.

Şu senaryoyu düşünelim. Konteyner içindesin ve ls -la /var/run/docker.sock dedin, dosya orada duruyor. İşte o an sistem yöneticisi için alarm zillerinin çalması gereken andır. Basit bir curl isteği ile host üzerinde yeni bir konteyner ayağa kaldırabilir ve host'un root dizinini (/) bu konteynerın içine mount edebilirim:

# Zararsızlaştırılmış (defanged) örnek saldırı konsepti
curl --unix-socket /var/run/docker.sock -H "Content-Type: application/json" \
  -d '{"Image": "alpine", "HostConfig": {"Binds": ["/:/mnt/host"]}}' \
  -X POST http://localhost/v1.41/containers/create

Bu istekten sonra tek yapmam gereken o konteynerı başlatmak ve /mnt/host/etc/shadow dosyasına ulaşıp şifreleri çekmek. Tebrikler, artık host sistemin sahibiyim.

Capabilities: Gereksiz Güç, Güç Değildir

Linux kernel'ı, root yetkilerini 'capabilities' adı verilen küçük parçalara böler. Bir konteynere CAP_SYS_ADMIN yetkisi verdiğinde, ona neredeyse her şeyi yapma izni vermiş olursun.

Sık yapılan hatalardan biri, uygulama düzgün çalışsın diye 'deneme-yanılma' yöntemiyle tüm yetkileri açmaktır. Oysa Red Team perspektifinde CAP_SYS_RAWIO veya CAP_SYS_PTRACE gibi yetkiler, kernel seviyesinde manipülasyon yapmak için biçilmiş kaftandır.

Eğer bir imajı hardened hale getirmek istiyorsan, ilk kuralın 'Least Privilege' olmalı. İhtiyacın olmayan tüm yetkileri drop etmelisin:

# docker-compose.yml veya Kubernetes Manifest örneği
securityContext:
  capabilities:
    drop:
      - ALL
    add:
      - NET_BIND_SERVICE

İmajların İçindeki Saatli Bombalar

Sadece runtime değil, imajın kendisi de bir risk. FROM python:latest yazdığında aslında neyi çektiğini biliyor musun? O imajın içindeki kütüphanelerin hangisinde kritik bir zafiyet (CVE) olduğunu takip ediyor musun?

testCompany içerisindeki projelerde sık sık rastladığımız bir durum: Uygulama kodu çok güvenli ama baz alınan imaj 3 yıl öncesinin zafiyetli bir Debian sürümü üzerine kurulu.

Bir saldırgan olarak, konteyner içine sızdığımda ilk yaptığım şeylerden biri paket yöneticisini (apt, apk, yum) kontrol etmektir. Eğer içeride gereksiz toollar (netcat, curl, wget, compilers) varsa, işimi çok kolaylaştırırsın. Multistage build kullanarak sadece ihtiyacın olan binary'leri prod ortamına taşımak bir opsiyon değil, zorunluluktur.

Runtime Koruması: Kimin Eli Kimin Cebinde?

Bir konteynerın normal şartlarda /etc altına yazma yapması veya beklenmedik bir IP adresine (örneğin 127.0.0.1 dışındaki bir dış IP'ye port taraması yapması) normal midir? Tabii ki hayır.

Seccomp (Secure Computing Mode) profilleri ve AppArmor/SELinux burada devreye giriyor. Bir konteynerın hangi system call'ları (syscall) yapabileceğini kısıtlamak, saldırganın elini kolunu bağlar. Örneğin, bir web uygulamasının mount veya reboot syscall'u yapmasına gerek yoktur. Bunları engellediğinde, exploit çalışsa bile payload'un sistemle etkileşime girmesini engellemiş olursun.

// Basit bir Seccomp profil kısıtlama örneği (defanged)
{
    "defaultAction": "SCMP_ACT_ERRNO",
    "architectures": [ "SCMP_ARCH_X86_64" ],
    "syscalls": [
        {
            "name": "read",
            "action": "SCMP_ACT_ALLOW"
        },
        {
            "name": "write",
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}

Sahadan Pratik Tavsiyeler

  1. Rootless Docker Kullan: Docker daemon'ını root yetkisi olmadan çalıştırabiliyorsan çalıştır. Bu, en büyük savunma hattıdır.
  2. Read-Only Root Filesystem: Konteynerın kendi dosya sistemine yazma yapmasına izin verme. Logları veya geçici verileri /tmp gibi kısıtlı alanlara (tmpfs) yönlendir.
  3. Network İzolasyonu: Her konteyner her konteynerla konuşmamalı. Docker network'lerini mikroservis mantığına göre izole et.
  4. Secret Yönetimi: Şifreleri, API keyleri asla ENV (environment variables) içinde tutma. Bunlar docker inspect ile kabak gibi görünür. Kubernetes Secret veya HashiCorp Vault gibi çözümlere yönel.

Konteyner güvenliği bir varış noktası değil, bir yolculuktur. Bugün 'güvenli' dediğin yapı, yarın yeni bir 'escape' tekniğiyle yerle bir olabilir. Bu yüzden savunmayı katmanlı kurmak, yani sadece Docker'a değil, host işletim sistemine ve network'e de güvenmemek gerekir.

Unutma, siber güvenlikte 'olmaz' dediğin şey, sadece henüz denenmemiş olandır.

İlgili yazılar