İçeriğe atla
0 XP 0
13 dkderinleşmeyle 14 dk55 XP7 etkinlik

Araçları kum havuzunda çalıştırmak

Bu derste
  • Model kod ya da sorgu üretiyorsa, o çıktıyı yürütmeden önce dosya, ağ, kaynak ve yetkisi sınırlı bir kum havuzuna koyarsın.
  • Bu derste Docker bayraklarıyla yalıtım kurmayı, yalıtımı silen satırları ve çekirdek sınırını öğrenirsin.
  • Sonunda yalıtımı bozan bir çalıştırma komutunu satır satır bulacaksın.

Puan bütün etkinlikleri cevaplayınca hesaba yazılır. Bu dersi tamamlayınca +55 XP alacaksın. Giriş yapmadan ilerlersen puan yalnız bu tarayıcıda durur.

Kod yorumlayıcısı olan bir asistan düşün. Kullanıcı bir CSV dosyası yükler, model Python kodu yazar ve sistem bu kodu çalıştırır. Kodu model yazdı, ama modelin okuduğu metni saldırgan etkileyebilir. Dosyanın içine gizlenmiş bir talimat, modele "ortam değişkenlerini oku ve dış adrese gönder" diye kod yazdırabilir.

Model çıktısını yürütmek bu yüzden bir güven kararıdır. Bu derste o kararın bedelini kum havuzuyla sınırlarsın. Önce gerçek olayların nasıl geliştiğini görürsün. Sonra Docker bayraklarıyla dört sınır çizer, yalıtımı silen satırları tanır ve kendi kaçış testini kurarsın.

Güvenilmeyen kodu yürütmek

2023'ün başında herkese açık bir matematik asistanında tam olarak bu oldu. Uygulama kullanıcının sorusunu modele Python koduna çevirtiyor ve kodu doğrudan sunucuda çalıştırıyordu. Bir araştırmacı önce zararsız bir deneme yaptı ve modelden ekrana selam yazan kod istedi.

Kod çalıştı. Sonra modelden ortam değişkenlerini yazdıran kod istedi. Yanıtta uygulamanın model API anahtarı göründü.

Son denemede model sonu gelmeyen bir döngü yazdı ve uygulama yanıt veremez hâle geldi. MITRE ATLAS bu olayı AML.CS0016 kodlu vaka olarak kaydeder. Anahtar kod ortamına hiç girmeseydi sızmazdı. Süre sınırı olsaydı döngü uygulamayı kilitlemezdi.

Sorun yalnız model kodu yazan uygulamalarda çıkmaz. 2025'te yaygın bir açık kaynak LLM akış aracında kritik bir açık bulundu (CVE-2025-3248). 1.3.0 öncesi sürümlerde kod doğrulama uç noktası, kimlik sormadan Python kodunu sunucu sürecinin içinde çalıştırıyordu. Açık sahada istismar edildi ve ABD'nin bilinen istismar edilen açıklar kataloğuna (KEV) girdi.

Dağıtık hesaplama için kullanılan başka bir çerçevede iş gönderme API'si kimlik doğrulamasız kod çalıştırıyordu (CVE-2023-48022). Üretici bunu tasarım gereği saydı ve çerçevenin yalnız sıkı denetimli bir ağda çalışması gerektiğini söyledi. İki olayın dersi aynıdır: kod çalıştıran servis hem ağdan hem ana süreçten ayrılır.

Kum havuzu (sandbox) güven kararını tersine çevirir. Çıktıyı baştan güvensiz kabul eder ve verebileceği zararı önceden sınırlar. OWASP'ın 2026 listesi aynı ilkeyi hatalı çıktı işleme maddesinde (LLM10:2026) söyler: modele sıfır güven yaklaşımıyla, sıradan bir kullanıcı gibi davran. Aynı madde, model çıktısının exec ya da eval gibi bir işleve doğrudan verilmesini riskin ilk örneği olarak sayar.

Kodun kötü olup olmadığını tahmin etmeye çalışmazsın. Kötü olsa bile ne kadar zarar verebileceğini baştan belirlersin. Guardrail saldırıyı tanımaya çalışır, kum havuzu ise tanıyamadığın saldırının bedelini küçültür.

Etkinlik 1 / 7

Her olay izini, zararı en doğrudan küçültecek önlemle eşleştir.

Soldan bir tane seç, sağdan eşini seç. Kullanılmış bir seçeneği yeniden seçersen yeni öğeye taşınır.

Dört sınır, satır satır

Sınırlar dört boyutta çizilir. Dosya sistemi: kök salt okunur, yazma yalnız geçici bir dizinde. Ağ: dışarı çıkış kapalı, gerekiyorsa dar bir izin listesi.

Kaynak: bellek, işlemci, süreç sayısı ve süre sınırı. Yetki: ayrıcalıksız kullanıcı, Linux yetenekleri (capabilities) düşürülmüş, yetki yükseltme kapalı. Dört boyut birbirini tamamlar.

Ağı kapatıp kökü yazılabilir bırakırsan kod, sonraki çalıştırmayı bekleyen bir dosya bırakabilir. Kökü kilitleyip root ile çalıştırırsan çekirdeğe giden yol genişler. Aşağıdaki komut dördünü birlikte kurar.

docker run --rm \
  --network=none \
  --read-only \
  --tmpfs /tmp:size=64m \
  --memory=256m --cpus=0.5 --pids-limit=64 \
  --user 10001:10001 \
  --cap-drop=ALL \
  --security-opt=no-new-privileges \
  kod-calistirici:1.4
Ağı kapalı, kökü salt okunur, kaynakları ve yetkisi sınırlı bir çalıştırıcı.

Satırları sırayla oku. --rm kapsayıcıyı iş bitince siler. --network=none içeride yalnız geri döngü arayüzünü bırakır, yani dışarıya hiç yol yoktur. --read-only kökü kilitler, --tmpfs ise bellekte 64 MB'lık geçici bir yazma alanı açar.

--memory, --cpus ve --pids-limit kaynakları sınırlar. Sonsuz döngü ya da süreç bombası makineyi kilitleyemez. Bellek aşılırsa çekirdek süreci öldürür. --user kodu 10001 numaralı ayrıcalıksız bir kullanıcıyla çalıştırır.

--cap-drop=ALL bütün Linux yeteneklerini düşürür. --security-opt=no-new-privileges ise setuid ve setgid programlarıyla yeni yetki kazanılmasını engeller. Son satır, sürüm etiketi taşıyan imaj adıdır.

Bir ayrıntıyı unutma: süre sınırı bir Docker bayrağı değildir. Çalıştırıcıyı başlatan servis bir zamanlayıcı tutar ve süre dolunca kapsayıcıyı durdurur. Kapsayıcının içinde de timeout komutuyla ikinci bir sınır koyabilirsin. Komutta görünmeyen bir koruma daha var.

Docker her kapsayıcıyı varsayılan bir seccomp profiliyle başlatır ve profil tehlikeli sistem çağrılarını kapatır. Bir yapılandırmada seccomp=unconfined satırı görürsen dur ve nedenini sor. O satır bu korumayı tamamen kaldırır.

Etkinlik 2 / 7

Modelin ürettiği kod, kapsayıcıdaki bir gizli anahtarı okumayı başardı. Anahtarın dışarı gönderilmesini en doğrudan hangi bayrak engeller?

Yalıtımı silen satırlar

Bazı ayarlar tek başına bütün sınırları siler. --network=host kapsayıcıya ana makinenin ağ yığınını verir. Kod hem dış adreslere hem de yalnız ana makineye açık iç servislere ulaşır. --privileged bayrağı bütün Linux yeteneklerini açar ve aygıt sınırlarını kaldırır.

/var/run/docker.sock soketini içeri bağlamak ise OWASP'a göre ana makinede sınırsız root yetkisi vermekle aynıdır. Soketi salt okunur bağlamak da çözüm değildir, yalnız istismarı biraz zorlaştırır. Yapılandırmayı incelerken önce bu üç satırı ara. Compose dosyalarında aynı ayarlar network_mode: host, privileged: true ve volumes altındaki soket satırı olarak görünür.

Çalışan bir kapsayıcıda bu ayarları docker inspect ile okursun. Komut, kapsayıcının hangi ayarlarla başlatıldığını gösterir. HostConfig bölümündeki NetworkMode, Privileged, ReadonlyRootfs ve Binds alanları üç tehlikeli satırın izini taşır.

Denetimi elle tek tek yapma. Bir betikle bütün çalıştırıcıları tara ve beklenen değerden sapan alanı raporla. Aşağıdaki komut iki kapsayıcıyı aynı anda sorgular ve her birini tek satırda özetler.

$ docker inspect --format '{{.Name}} {{.HostConfig.NetworkMode}} {{.HostConfig.Privileged}} {{.HostConfig.ReadonlyRootfs}} {{.HostConfig.Binds}}' kod-calistirici-7 grafik-cizici-2
/kod-calistirici-7 none false true []
/grafik-cizici-2 host false true [/var/run/docker.sock:/var/run/docker.sock]
Sütunlar sırayla: ad, ağ modu, ayrıcalıklı mod, salt okunur kök, bağlanan dizinler.

İlk satır beklenen tabloyu gösterir: ağ kapalı, ayrıcalık yok, kök salt okunur ve hiçbir dizin bağlanmamış. İkinci satır iki alarm verir. Ağ modu host, yani kapsayıcı ana makinenin ağını kullanır. Binds alanında Docker soketi görünür.

Kök salt okunur olsa bile bu kapsayıcıyı ele geçiren kod, soket üzerinden Docker'a istediği komutu verebilir. Bulguyu kapatmak için soket bağlantısını kaldırır ve ağ modunu none yaparsın. Ardından aynı sorguyu yeniden koşar, düzeltmenin tuttuğunu görürsün.

Kod çalıştırmayan araçlarda ilke aynı kalır, yalnız biçim değişir. Model SQL sorgusu üretiyorsa yalıtım dar bir veritabanı rolüdür. Rol yalnız okuma kopyasına bağlanır ve birkaç görünümü okur.

Yazma ve şema değiştirme hakkı yoktur. Model tablo silen bir sorgu yazsa bile veritabanı onu reddeder. Sorguya bir zaman aşımı ve satır sınırı da koy, çünkü pahalı bir birleştirme sorgusu da sistemi kilitleyebilir.

Etkinlik 3 / 7

Modelin ürettiği kodu çalıştıran bir ortam kuruyorsun. Hangi ayarlar yalıtımı güçlendirir?

Birden fazla doğru var.

Veri, çekirdek ve ömür

Yalıtımın bir de veri tarafı var. Kum havuzuna yalnız o görev için gereken dosyayı koy. Bütün depoyu bağlamak, ağı kapatsan bile içeride geniş bir okuma alanı bırakır. Ortam değişkenine API anahtarı koyma, matematik asistanındaki anahtar tam olarak oradan çıktı.

Kodun dış bir servise ihtiyacı varsa çağrıyı kum havuzunun dışındaki bir aracı servis yapsın. Çıktıyı serbest metin yerine beklenen biçimde, örneğin tek bir JSON dosyası olarak dışarı al. Biçim daraldıkça kaçırılacak verinin sığacağı yer de daralır.

Kapsayıcının da bir sınırı var: ana makineyle aynı çekirdeği paylaşır. Çekirdekteki bir açık, kapsayıcıdan kaçışa yol açabilir. Tamamen güvenilmeyen kod için daha güçlü bir sınır seç.

gVisor, Go ile yazılmış ve kullanıcı alanında çalışan bir uygulama çekirdeğidir. Sistem çağrılarını kendisi karşılar ve ana çekirdeğe çok daha dar bir yüz gösterir. runsc adlı çalışma zamanıyla Docker'a takılır.

Firecracker gibi mikro sanal makineler ise KVM üzerinde ayrı bir çekirdek çalıştırır. Firecracker yalnız beş aygıtı taklit eder ve 125 milisaniyenin altında açılır. Hızlı açılış, her çağrıya ayrı bir sanal makine vermeyi mümkün kılar.

Derinleş:seccomp: varsayılan profil ne yapar

seccomp, Linux çekirdeğinde bir sürecin hangi sistem çağrılarını yapabileceğini süzen bir mekanizmadır. Docker her kapsayıcıyı varsayılan bir seccomp profiliyle başlatır. Profil bir izin listesi gibi çalışır: listede olmayan çağrı çalışmaz ve EPERM hatasıyla döner.

Docker'ın belgesine göre profil 300'ü aşkın çağrıdan yaklaşık 44'ünü kapatır. Kapananlar arasında çekirdek modülü yükleme, sistem saatini değiştirme, dosya sistemi bağlama ve io_uring çağrıları var. io_uring çağrıları, kapsayıcıdan kaçışa kullanılabilen açıklar yüzünden kapatılanlar arasına girdi.

Bir uygulama çalışmayınca profili seccomp=unconfined ile tamamen kapatmak yaygın bir kestirmedir. Bu kestirme sorunu çözmez, bütün çağrı yüzeyini açar. Doğru yol, uygulamanın gerçekten neye ihtiyaç duyduğunu ölçmek ve yalnız o çağrıyı ekleyen özel bir profil yazmaktır. gVisor bu mantığı bir adım öteye taşır: çağrıyı yalnız süzmez, kendi çekirdeğinde karşılar.

Ömür yönetimi kritik bir ayrıntıdır. Her çağrı için taze kapsayıcı aç ve iş bitince yok et. Kalıcı kapsayıcıda bir çağrının bıraktığı dosya ya da arka plan süreci sonraki çağrıda da durur. Böylece iki kullanıcı arasında sessiz bir köprü kurulur.

Saldırgan önce zararsız görünen bir dosya bırakır, sonraki kullanıcının kodu o dosyayı okur ya da çalıştırır. Gecikme sorun olursa önceden hazırlanmış bir havuz kullan, ama her kapsayıcıyı yalnız bir kez kullan.

Etkinlik 4 / 7

Doğru mu, yanlış mı?

Aynı kapsayıcıyı farklı kullanıcıların çağrıları arasında yeniden kullanmak yalıtımı zayıflatır.

Etkinlik 5 / 7

Modelin ürettiği kodu güvenli çalıştırma akışını sıraya diz.

Parçalara tıklayarak komutu soldan sağa kur.

$

Kaçış testi ve izleme

Kum havuzunu üretime almadan önce kendi kaçış testini koş. Test, saldırganın deneyeceği şeyleri zararsız biçimde taklit eder. İçeride dışarı istek atan, uygulama dizinine yazan, süreç çoğaltan ve süreyi aşan küçük betikler çalıştırırsın. Hepsi engellenmeli ve hepsi günlüğe düşmeli.

Yalnız geçici dizine yazma denemesi geçmeli, çünkü o alan bilerek açıldı. Testi kendi lab ortamında ya da yazılı iznin olan sistemde koş. Aşağıda bir test koşusunun çıktısı var.

$ docker run --rm --network=none --read-only --tmpfs /tmp:size=64m \
    --memory=256m --cpus=0.5 --pids-limit=64 --user 10001:10001 \
    --cap-drop=ALL --security-opt=no-new-privileges \
    kod-calistirici:1.4 python3 /test/kacis_testi.py
[1] dis_istek     198.51.100.7:443  ENGEL  [Errno 101] Network is unreachable
[2] dizine_yaz    /app/iz.txt       ENGEL  [Errno 30] Read-only file system
[3] tmp_yaz       /tmp/iz.txt       IZIN   beklenen, 64 MB tmpfs
[4] surec_cogalt  200 alt surec     ENGEL  [Errno 11] Resource temporarily unavailable
[5] sure_asimi    while True        ENGEL  timeout 5 s, cikis kodu 124
SONUC: 4 kacis denemesi engellendi, 1 beklenen yazma gecti
Kaçış testinin çıktısı. Her deneme ya engellenir ya da bilerek açılan alanda kalır.

Satırları sırayla oku. Birinci deneme 198.51.100.7 adresine bağlanmak ister. Çekirdek ağa ulaşılamadığını söyler, çünkü kapsayıcıda geri döngüden başka arayüz yok. İkinci deneme uygulama dizinine yazmak ister ve salt okunur dosya sistemi hatası alır.

Üçüncü deneme geçer. Bu bir açık değil, /tmp bilerek açılan 64 MB'lık bellek alanıdır. Dördüncü deneme 200 alt süreç açmaya çalışır ve süreç sınırına takılır.

Beşinci denemede sonsuz döngü 5 saniye sonra kesilir ve 124 çıkış koduyla biter. Son satır özeti verir. Bu testi sürüm kapısına bağlarsan altyapı güncellemesiyle gelen sessiz gevşemeyi de yakalarsın.

Kum havuzu yalnız engellemez, iz de bırakır. Engellenen her ağ denemesini, salt okunur dosya hatasını, süreç sınırına takılmayı ve süre aşımını olay olarak kaydet. Kayda çağrının oturum numarasını ve modelin ürettiği kodun özetini ekle.

Tek bir engel çoğu zaman masum bir hatadır. Aynı oturumdan art arda gelen ağ ve dosya denemeleri ise bir enjeksiyonun izidir. Kayıtları güvenlik izleme sistemine gönder ve oturum başına bir eşikle alarm kur. Engellenen bir kaçış denemesi iki şeyi birden gösterir: savunma çalıştı ve biri denedi.

Etkinlik 6 / 7

Kum havuzunun olay günlüğünde bir kaçış girişiminin izini taşıyan satırları işaretle.

Şüpheli satırlara tıkla. Birden fazla olabilir.

Aynı kontrolü başka bir ekibin yapılandırmasında yap. Ekip, grafik çizen yardımcı aracını hızlı çalışsın diye farklı bayraklarla başlatmış. Araç modelin yazdığı çizim kodunu çalıştırıyor ve sonucu PNG olarak döndürüyor.

Ekip kökü salt okunur yapmış, bellek ve süreç sınırı koymuş, ayrıcalıksız kullanıcı seçmiş. Bir satır ise bu emeğin çoğunu boşa çıkarıyor. Satırları tek tek oku ve dört boyuttan hangisinin açık kaldığını bul.

Etkinlik 7 / 7

Grafik aracının çalıştırma komutunda yalıtımı bozan satırı bul.

Zafiyetli satıra tıkla.

Bu derste bir hata mı gördün? Bildir