Konteyner bir sınır değildir
Bir konteyner üç şeyden oluşur: bir namespace (process, mount, network ve geri kalanı; ne görebildiğini yalıtır), bir cgroup (ne kullanabildiğini sınırlar) ve bir dizi capability (ne yapmana izin verildiğini belirler). Bunların hiçbiri tek başına bir güvenlik sınırı değildir — hepsi kaynak yalıtımı için tasarlanmış ilkel yapılardır. Host ile konteyner aynı çekirdeği paylaşır ve bu paylaşım, kaçışın neden mümkün olduğunun da temelidir. Namespace’ler görünürlüğü, cgroup’lar tüketimi, capability’ler ise yetkiyi sınırlar; ama üçü de aynı çekirdeğin üzerinde çalışır. Dolayısıyla koruma, konteynerin ne kadar iyi yapılandırıldığına bağlıdır, sihirli bir yalıtım katmanına değil.
Bu yüzden her konteyner kaçışı aynı biçimi izler: konteynere verilen ve aslında verilmemesi gereken tek şeyi bul, sonra onu host’un dosya sistemine ya da process alanına ulaşmak için kullan. Burada en yaygın iki yolu gerçek bir Docker daemon’ı üzerinde gösteriyorum; ardından artık geçerli olmayan üçüncü bir yolu, neden öldüğünü anlatarak kapatıyorum.
Bu laboratuvardaki host hakkında bir not: Mac’imde Docker, bir Linux VM’inin (OrbStack) içinde çalışıyor; bu yüzden bu çıktılardaki “host” aslında o VM’dir. Bu makinelerin normalde çalıştığı yer olan bir Linux sunucusunda ise aynı adımlar doğrudan fiziksel makineye iner. Yani mekanizma değişmez, yalnızca hedefin kim olduğu değişir.
Docker socket: host’un anahtarları, içeride mount edilmiş
/var/run/docker.sock, Docker daemon’ının kontrol düzlemidir. Host’ta root ile eşdeğerdir, çünkü onunla konuşabilen herkes host üzerinde istediği ayrıcalıkta konteyner oluşturabilir. Bu socket’i bir konteynerin içine mount etmek, en yaygın kaçış tetikleyicisidir:
$ docker run -d --name web -v /var/run/docker.sock:/var/run/docker.sock alpine sleep 3600
Konteynerin içinde socket hazırdır ve yanıt verir:
$ ls -la /var/run/docker.sock
srw-rw---- 1 root root 0 Nov 3 07:38 /var/run/docker.sock
$ docker -H unix:///var/run/docker.sock ps
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
0da093ef619f alpine "sleep 3600" 1 second ago Up 1 second web
O ps çıktısı bile host seviyesinde bilgi gösteriyor; konteyner artık kendi küçük dünyasından değil, daemon’ın gözünden konuşuyor. Kaçış da bariz bir sonraki adım oluyor: socket’i kullanarak host’un root dosya sistemini içine mount eden yeni bir konteyner başlat, sonra onun içine chroot yap. Burada dikkat edilmesi gereken nokta, kaçışın ilk konteynerin içinden değil, onun adına konuşan daemon üzerinden gerçekleşmesidir:
$ docker -H unix:///var/run/docker.sock run --rm --privileged -v /:/host alpine \
sh -c "cat /host/etc/hostname; head -1 /host/etc/os-release"
[host] hostname: orbstack
[host] os: PRETTY_NAME="OrbStack"
Hiçbir zaman kendisine verilmeyen host /etc/hostname dosyasını bir konteynerin içinden okumak, işin bütün özetidir. cat komutunu cat /host/etc/shadow ile değiştir ya da root sahipli bir dosya yaz; konteyner sınırı artık yoktur. Socket anahtardı, geri kalan her şey onu takip etti. Bu yüzden Docker socket’i “sadece bir dosya” gibi düşünmek yanıltıcıdır: o dosya, host’un tüm kontrolünü temsil eder. Okuma izni bile bilgi sızdırabilir; yazma izni ise doğrudan host üzerinde kod çalıştırma demektir.
--privileged: diski doğrudan mount et
Konteyner privileged ise socket’e ihtiyacın bile yoktur. --privileged hem CAP_SYS_ADMIN verir hem de device cgroup kısıtlamalarını kaldırır; bu ikisi birlikte “host block device’larını mount edebilirsin” anlamına gelir. Diskleri listele, host root’u mount et, işlem tamam:
$ docker run --rm --privileged alpine sh -c 'cat /proc/partitions'
254 0 420164 vda
254 16 482798608 vdb
254 17 482797568 vdb1
$ docker run --rm --privileged alpine sh -c 'mkdir -p /mnt/host && mount /dev/vdb1 /mnt/host && ls /mnt/host'
docker
guest-state
k8s
scon
version
/dev/vdb1 host’un ana bölümüdür ve ls /mnt/host onu listeler — yani daemon’ın kendi root dosya sistemini. Akılda tutulması gereken nüans şu: --cap-add=SYS_ADMIN tek başına yeterli değildir. Mount başarısız olur, çünkü ayrıcalıklı olmayan bir konteyner ilgili device node’a da sahip değildir:
$ docker run --rm --cap-add=SYS_ADMIN alpine sh -c 'mount /dev/vdb1 /mnt/host'
mount: mounting /dev/vdb1 on /mnt/host failed: No such file or directory
Yani gerçek malzeme capability + device erişimi bileşimidir. --privileged ikisini aynı anda verir; tam da bu yüzden kaçınılması gereken bayraktır. Tek tek --cap-add ve --device vermek, ihtiyaç duyulanı verirken gereksiz yüzeyi kapatmanın daha kontrollü yoludur. Kural basit: bir konteyner yalnızca gerçekten ihtiyaç duyduğu capability’ye ve yalnızca gerçekten erişmesi gereken aygıta sahip olmalıdır.
Modern sistemlerin çoktan kaldırdığı numara
Üçüncü klasik kaçış, cgroup v1 release_agent mekanizmasıydı: bir cgroup mount et, notify_on_release ayarla, release_agent değerini host’taki bir script’e yönlendir ve sonra tetikle. Bu sistemde ise mekanizma daha başlamadan ortadan kalkmış durumda:
$ cat /proc/self/cgroup
0::/
Tek bir 0::/ çıktısı cgroup v2 anlamına gelir ve cgroup v2 release_agent mekanizmasını tamamen kaldırmıştır. Bu, konteyner modelinin zamanla nasıl sıkılaştığını gösteren iyi bir örnektir: cgroup v1’e karşı çalışan bir kaçışın artık bir arayüzü yoktur. Ders “cgroup v1 kullanmak tehlikeliydi” değil; konteynerin sunduğu arayüz yüzeyi bir saldırganın yaşadığı yerdir ve o yüzeyi küçültmek tek gerçek savunmadır. Bir zamanlar çalışan bir teknik, arayüz kaldırıldığında kendiliğinden etkisiz hale gelir; yama yazmak zorunda kalmazsın, çünkü saldırının tutunacağı yüzey artık yoktur.
Tespit ve düzeltme
| Kaçış | Gerektirdiği | Tespit | Kontrol |
|---|---|---|---|
| Docker socket | mount edilmiş /var/run/docker.sock | pod içinden beklenmedik konteyner oluşturma | socket’i asla mount etme; delegated builder servisi kullan |
| Privileged mount | --privileged (SYS_ADMIN + devices) | konteynerden host block device mount’u | --privileged’ı kaldır; yalnızca gerekli --cap-add + --device ver |
| cgroup release_agent | cgroup v1 | tarihsel | cgroup v2 arayüzü kaldırır |
Kontroller temiz bir hiyerarşi oluşturur. Platform seviyesinde Pod Security Standards (“restricted” profili) privileged mode’u, host mount’larını ve capability escalation’ı engeller. Runtime seviyesinde seccomp ve AppArmor, konteynerin çağırabileceği syscall’ları sınırlar; böylece beklenmedik bir mount başarılı olmadan ölür. Alışkanlık seviyesinde ise iki basit kural vardır: Docker socket’ini bir konteynere asla mount etme ve yazılı bir gerekçen olmadan --privileged çalıştırma. Konteyner kaçışı sonuçta bir zafiyet sorunundan çok bir yanlış yapılandırma sorunudur; bu yüzden düzeltmeler de çoğu zaman bir yamadan değil, verilen iznin geri alınmasından ibarettir.
Özet
İki canlı kaçış, bir kullanımdan kalkmış arayüz: Docker socket root ile eşdeğerdir ve asla dağıtılmamalıdır; --privileged host’a yalnızca bir disk mount uzağındadır; cgroup v1 release_agent numarası ise cgroup v2 onu kaldırdığı için artık yoktur. Ortak nokta şudur: her kaçış verilmiş bir capability, mount ya da socket ile başlar — bu yüzden düzeltme, onları vermeyi bırakmaktır. Yüzeyi daraltmak, tek tek saldırıları kovalamaktan daha kalıcı bir savunma sağlar.
--privileged altındaki blok cihaz erişimi.Kanıt ve sınırlar
Docker socket ve --privileged kaçış adımları, yerel bir OrbStack Linux sanal makinesi üzerinde çalışan izole bir Docker daemon ortamında yürütülmüştür. Bu nedenle mount edilen kök dosya sistemi (/dev/vdb1) ve /etc/os-release çıktıları, üretim ortamındaki bir küme düğümünü değil yerel daemon VM’ini yansıtır. Negatif kontroller (cihaz erişimi olmadan --cap-add=SYS_ADMIN hatası ve cgroup v2’yi doğrulayan 0::/ çıktısı) aynı çekirdek üzerinde doğrulanmış, test konteynerleri de doğrulamadan hemen sonra kaldırılmıştır.
Referanslar
- Docker, Security: docs.docker.com/engine/security/security
- Kubernetes, Pod Security Standards: kubernetes.io/docs/concepts/security/pod-security-standards
- Linux man-pages, cgroups (7): man7.org/linux/man-pages/man7/cgroups.7.html
- MITRE ATT&CK, Container Escape (T1611): attack.mitre.org/techniques/T1611
- Trail of Bits, Understanding Docker Container Escapes: blog.trailofbits.com
Bu teknik not ne kadar güncel?
En son kaynak incelemesi, içerik güncellemesi veya yayın tarihi gösterilmektedir.
Birincil kamu kayıtları kontrol edildi. Ortama özgü davranış, ayrıca yeniden üretilmedikçe iddianın dışındadır.

