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

container-security

Zırhlı Araç mı, Cam Kavanoz mu? Konteynerlarda İzolasyonun İllüzyonu Üzerine

Konteynerların sunduğu o sahte güvenlik hissine kapılıp kapıları açık bırakmak... Saat gece 03:14'te bir syscall anomalisiyle başlayan o teknik krizin anatomisini ve modern izolasyon tekniklerini inceliyoruz.

Sedat Özdemir
· 4 dk okuma

Saat gece 03:14, ekranımda akan loglarda tuhaf bir syscall anomalisi belirdi. testCompany altyapısındaki bir Kubernetes pod'u, normalde hiç işi olmayan /proc/kcore dosyasına erişmeye çalışıyordu. O an kahve kupasını masaya bırakıp terminale eğildim. Birileri mikroservis mimarisinin o tatlı esnekliğini, ana makineye (host) sızmak için bir basamak olarak kullanmaya çalışıyordu. Konteyner güvenliği dediğimiz şeyin sadece 'image scan' yapmaktan ibaret olmadığını anladığımız o kritik anlardan biriydi bu.

Birçok kişi konteynerları, içindeki her şeyi dış dünyadan tamamen izole eden büyülü birer kutu sanıyor. Ama işin aslı pek öyle değil kanka. Konteynerlar, sanal makineler (VM) gibi kendi çekirdeğine (kernel) sahip değiller; hepsi aynı kernel'ı paylaşıyor. Bu da demek oluyor ki, eğer kapıyı tam kapatmazsan, içerideki 'root' kullanıcı bir gün gelir senin host sisteminin de efendisi olur.

O Meşhur 'Privileged' Modu: Kendi Ayağına Sıkmak

Bakın, sahada en çok gördüğüm hata ne biliyor musunuz? Geliştiricinin işi kolaylaşsın diye pod'a ya da konteynere privileged: true yetkisi verilmesi. Bu, saldırgana 'Al kardeşim, bu sistemin anahtarları sende, ne istersen yap' demekle aynı şey.

Bir Red Teamer olarak benim ilk baktığım yer /dev klasörüdür. Eğer bir konteyner içerisinde ls /dev dediğimde ana makinenin disklerini görüyorsam, o iş bitmiştir.

Zararsızlaştırılmış bir breakout örneğine bakalım (Sakın prod ortamında deneme!):

# Zararlı bir konteyner başlatma senaryosu (Temsili)
# docker run --privileged --net=host -it alpine sh

# Konteyner içinden host diskine erişim denemesi
mkdir /mnt/host_root
mount /dev/sda1 /mnt/host_root

# Artık host sistemin /etc/shadow dosyasına ulaştık bile!
cat /mnt/host_root/etc/shadow

Buradaki olay şu: Konteyner içindeki kullanıcı root ise ve CAP_SYS_ADMIN gibi tehlikeli capability'lere sahipse, izolasyon bir anda kağıttan bir kuleye dönüşüyor.

Docker Socket Faciası

Bir diğer 'klasik' hata ise docker.sock dosyasının konteyner içine mount edilmesi. Genellikle monitoring araçları ya da CI/CD tool'ları için yapılır bu. Ama saldırgan o socket'e ulaştığı an, Docker API'si üzerinden yeni bir konteyner yaratıp host üzerinde kod çalıştırabilir.

Defanged bir payload mantığı şöyle işler:

# Docker socket erişimi varsa
curl --unix-socket /var/run/docker.sock -H "Content-Type: application/json" \
  -d '{"Image": "alpine", "Cmd": ["cat", "/etc/hostname"], "HostConfig": {"Binds": ["/:/host"]}}' \
  -X POST http://localhost/v1.24/containers/create

Buradaki Binds: ["/:/host"] kısmı her şeyi özetliyor. Host'un root dizinini yeni konteynerin içine bağla ve içeri gir. Oyun bitti.

Savunma Hattını Nasıl Kurarız?

Şimdi biraz da 'nasıl korunalım?' kısmına odaklanalım. testCompany tarafında bu tür krizlerin önüne geçmek için uyguladığımız birkaç altın kural var.

1. Rootless Konteynerlar

Eğer bir konteyneri root kullanıcısıyla çalıştırmıyorsan, saldırganın manevra alanı %90 oranında kısıtlanır. Dockerfile'ına şu basit satırı eklemek hayat kurtarır:

# Güvenli bir Dockerfile örneği
FROM node:18-alpine

# Gereksiz paketleri sil, sadece runtime'ı bırak
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

USER appuser
WORKDIR /home/appuser

COPY --chown=appuser:appgroup . .

CMD ["node", "index.js"]

2. Capability'leri Budamak

Linux kernel'ı, root'un yetkilerini 'capabilities' dediğimiz parçalara böler. Varsayılan olarak Docker birçok capability ile gelir. Bizim yapmamız gereken 'en az yetki prensibi' (Least Privilege) gereği hepsini drop edip, sadece gerekeni vermek.

Kubernetes için örnek bir SecurityContext:

securityContext:
  allowPrivilegeEscalation: false
  runAsNonRoot: true
  capabilities:
    drop:
      - ALL
    add:
      - NET_BIND_SERVICE # Sadece 80 portunu açman gerekiyorsa bunu ekle

3. Image Scanning ve Distroless

Kullandığın base image ne kadar büyükse, içinde o kadar çok açık (CVE) barındırır. ubuntu:latest kullanmak yerine distroless image'ları tercih etmelisin. Distroless image'larda shell (/bin/sh veya /bin/bash) bile yoktur. Saldırgan içeri sızsa bile çalıştırabileceği bir komut satırı bulamaz. Karşısında sadece binary dosyan olur.

Ağ Katmanında İzolasyon (Network Policies)

Konteynerlar arasındaki trafik varsayılan olarak serbesttir. Yani 'frontend' pod'una sızan biri, ağ üzerinden direkt 'database' pod'una zıplayabilir (Lateral Movement). Bunu engellemek için Kubernetes Network Policy kullanmak şart.

Örneğin, sadece belirli bir etiketli (label) servisin database'e erişmesine izin veren bir kural:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-policy
spec:
  podSelector:
    matchLabels:
      role: db
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: api

Runtime Security: Canlı İzleme

O gece 03:14'teki saldırıyı nasıl fark ettiğimize gelelim. Statik taramalar (Image scan) çalışmayan kodun açığını bulur. Ama asıl tehlike 'runtime'da, yani uygulama çalışırken ortaya çıkar. Falco gibi araçlar, kernel seviyesindeki syscall'ları izleyerek 'Bir konteyner neden /etc/shadow dosyasını okumaya çalışıyor?' diye bağırır.

Siber güvenlik bir varış noktası değil, bitmek bilmeyen bir yolculuk. Konteynerlar bize müthiş bir hız kazandırıyor ama bu hızı yaparken emniyet kemerini takmayı unutmamak lazım. İzolasyonun bir illüzyon olduğunu kabul edip, savunma katmanlarını (defense in depth) bu bilinçle inşa etmeliyiz.

Bir sonraki krizde görüşmek üzere, sistemlerinizi güncel tutun.

İlgili yazılar