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

Sızdırılan sırlar: kodda ve imajda

Bu derste
  • Sırların kod deposundan, imaj katmanından ve boru hattından nasıl sızdığını, gerçek sırrı tanımayı, BuildKit sırlarını ve sızıntıda doğru sırayı öğren.
  • Git geçmişi ve imaj katmanları silinmiş sanılan sırrı saklar.
  • Tek gerçek düzeltme sırrı iptal etmektir; temizlik ikinci adımdır.

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

Bir saldırgan neden şifrelemeyle uğraşsın? Aynı anahtar çoğu zaman bir kod deposunda, bir konteyner imajında ya da bir boru hattı günlüğünde düz metin durur. Bulut hesabına giden en kısa yol çoğu zaman sızmış bir erişim anahtarıdır. Önce gerçek sırrı yanlış pozitiften ayırmayı öğreneceksin.

İmaj katmanında kalmış bir sırrı komutla okuyacak, derleme sırrını katmana yazmadan kullanacaksın. Sızıntı anında hangi adımın önce geldiğini ve süreci nasıl düzelteceğini de göreceksin. En sonda sırrı hiç taşımayan bir mimariye bakacaksın.

Sır nerede sızar, gerçek sır nasıl tanınır

Sır (secret), bir sisteme erişim veren her değerdir: parola, API anahtarı, erişim jetonu, özel anahtar ya da bağlantı dizesi. Dört yerden sızar. Birincisi kod deposudur; geliştirici hızlı bir deneme için anahtarı dosyaya yazar ve silmeyi unutur.

İkincisi konteyner imajıdır; derlemede kullanılan jeton bir katmanda kalır. Üçüncüsü boru hattı günlüğüdür; hata ayıklama modundaki bir betik ortam değişkenlerini ekrana basar. Dördüncüsü istemciye giden koddur.

Mobil paketteki ya da tarayıcıya giden JavaScript'teki her anahtar herkese açıktır, orada dar yetkili bir vekil uç nokta durmalıdır. Ölçek büyüktür. GitGuardian'ın 2025 raporuna göre 2024'te herkese açık GitHub depolarına 23,7 milyonu aşkın yeni sır eklendi.

Boru hattı günlükleri ayrı bir dikkat ister. GitHub Actions gibi sistemler sır olarak tanımlanan değerleri günlükte maskeler. Ama maske yalnız değerin birebir kendisini tanır.

Sır base64 ile kodlanıp basılırsa ya da parçalara bölünürse maske tutmaz. Bash betiğinde set -x açıkken her komut değişkenleriyle birlikte günlüğe yazılır. Hata ayıklamak için eklenen env ya da printenv satırı bütün ortamı döker.

Günlükler çoğu zaman geniş bir ekibe açıktır ve aylarca saklanır. Bu yüzden üretim boru hattında hata ayıklama modunu kapatır, günlük erişimini daraltır ve günlükleri de sır taramasından geçirirsin. İstemci tarafında kural daha serttir: tarayıcıya ya da mobil pakete giden hiçbir değer gizli kalmaz.

Taramada parola ya da token kelimesi geçen her satır sızıntı sayılmaz. Gerçek sır, değeri doğrudan koda yazılmış bir kimlik bilgisidir ve üç işaretle kendini ele verir. Birincisi bilinen öneklerdir. AWS'nin kalıcı erişim anahtarı kimliği AKIA, geçici kimliği ASIA ile başlar ve 20 karakterdir.

GitHub'ın klasik kişisel erişim jetonu ghp_, GitLab'inki glpat- ile başlar. İkincisi yüksek entropidir, yani uzun ve rastgele görünen bir dize.

Üçüncüsü çalışan bir jeton biçimidir. Ortam değişkeninden okuyan satır, sahte test verisi ya da kurulum belgesi ise sızıntı değildir, çünkü değer koda hiç yazılmamıştır. İyi tarayıcılar bulunan anahtarın hâlâ geçerli olup olmadığını da sınar.

Etkinlik 1 / 7

Depo taramasının çıktısında gerçek sır sızıntısı olan satırları işaretle.

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

Etkinlik 2 / 7

Tarama çıktısından yalnız AWS erişim anahtarı kimliklerini yakalayan bir desen yaz. Kimlik AKIA ya da ASIA ile başlar ve toplam 20 büyük harf ya da rakamdır.

Yazdıkça eşleşen satırlar işaretlenir. Hedef: yalnız doğru satırlar eşleşsin.

deploy.sh:12 AKIAIOSFODNN7EXAMPLE eşleşmedi
ci.yml:21 ghp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8 eşleşmedi
oturum.json:4 ASIAY34FZKBOKMUTVV7A eşleşmedi
README.md:9 AKIA ile başlayan anahtarları koda yazma eşleşmedi
test/sahte.env:2 AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} eşleşmedi
notlar.md:3 AKIA1234 eşleşmedi

Çözümlü örnek: imaj katmanında kalan sır

Konteyner imajı üst üste binen katmanlardan oluşur. Dockerfile'daki her RUN, COPY ve ADD talimatı yeni bir katman üretir. Sonraki bir katmanda dosyayı silmek onu önceki katmandan çıkarmaz, yalnız üstünü örter. İmajı indiren herkes katmanları tek tek açabilir.

Build argümanları (ARG) ve ortam değişkenleri de iz bırakır. Docker'ın belgesi bunu açıkça yazar: build argümanları ve ortam değişkenleri sır taşımaya uygun değildir, çünkü son imajda kalırlar. GitGuardian'ın aynı raporu, 15 milyon herkese açık Docker Hub imajında 100.000 geçerli sır bulduğunu söyler; bunların 7.000'i aşkını AWS anahtarıdır. Aşağıda lab/uygulama:son imajının geçmişine bakıyorsun.

$ docker history --no-trunc --format '{{.CreatedBy}}' lab/uygulama:son
CMD ["python" "app.py"]
COPY . . # buildkit
RUN |1 PIP_TOKEN=glpat-AbCdEf123456 /bin/sh -c pip install --index-url https://token:[email protected]/simple -r gereksinim.txt # buildkit
ARG PIP_TOKEN=glpat-AbCdEf123456
WORKDIR /app
(taban imaj katmanları kısaltıldı)
lab/uygulama:son imajının geçmişi. En yeni katman en üsttedir.

Çıktıyı alttan yukarı oku, çünkü docker history en yeni katmanı en üstte gösterir. ARG satırı varsayılan değeriyle birlikte kayda geçmiş, jeton düz metin duruyor.

RUN satırının başındaki |1 PIP_TOKEN=... kısmı, komutun çalıştığı andaki build argümanını gösterir. Yani varsayılan değer vermesen ve jetonu --build-arg ile geçsen bile değer yine bu satıra yazılır. --no-trunc bayrağı uzun satırları kırpmadan basar. İmajı çekebilen herkes aynı komutu çalıştırır.

Sızan değer, glpat- önekinden anlaşıldığı gibi bir GitLab kişisel erişim jetonudur. Doğru tepki önce jetonu iptal etmektir. Sonra imajı güvenli yöntemle yeniden derler ve eski etiketleri kayıt defterinden kaldırırsın.

Etkinlik 3 / 7

Bu Dockerfile'da sırrı imaja gömen satırı bul.

Zafiyetli satıra tıkla.

Sızıntı anında doğru sıra

Sızıntı bulunduğunda sıra nettir. Önce sır iptal edilir ve yenisi üretilir. Ancak ondan sonra geçmiş temizliği, tarama ve süreç düzeltmesi gelir.

Ters sıra, sızmış anahtarı saatlerce geçerli bırakır. İptalden sonraki iş etki analizidir. Sızan bir AWS anahtarıysa CloudTrail'de o anahtar kimliğiyle yapılan çağrıları ararsın.

Anahtarın sızdığı andan bu yana hangi API'lerin çağrıldığına bakarsın. Yeni bir kullanıcı, erişim anahtarı ya da rol oluşturulmuşsa saldırgan kalıcılık kurmaya çalışmıştır. Ele geçen anahtarla atılan ilk adım çoğu zaman kimlik sorgusudur, yani sts get-caller-identity. Bu çağrı kendi başına zararsızdır ama bilinmeyen bir adresten geliyorsa güçlü bir işarettir.

Kod deposunda silmek yetmez. Git geçmişi silinen satırı tutar, çatallar ve yerel kopyalar da onu taşır. GitHub'ın kendi belgesi sıralamayı açıkça yazar: sır bir parola ya da jetonsa ilk adım onu iptal etmek ya da döndürmektir.

Geçmişi yeniden yazmak ondan sonra gelir ve git-filter-repo aracıyla yapılır. Ama başkalarının kopyalarından ve çatallarından sırrı silemezsin. Önbellekteki commit görünümleri ve eski çekme isteklerinde (pull request) kalan kopyalar için GitHub Destek'ten temizlik istersin.

Tarayıcı botları herkese açık depolara gelen commit'leri sürekli izler. Bu yüzden sızan anahtar sızmış kabul edilir ve tartışmadan iptal edilir. Temizlik yalnız sırrın yeniden kopyalanmasını zorlaştırır, iptalin yerini tutmaz.

Olay anında sıralama önceden yazılır. Hangi sır türünün nasıl iptal edildiği, kimin yetkili olduğu ve yerine yenisinin nasıl dağıtıldığı bir sayfada durur. AWS erişim anahtarı için adımlar kısadır: anahtarı devre dışı bırakırsın, yeni kimliği dağıtırsın, çalışan her şeyin yeni kimliğe geçtiğini doğrular ve eski anahtarı silersin. GitHub jetonu sahibinin ayarlarından ya da kuruluş yöneticisince iptal edilir.

Gece yarısı bulunan bir anahtar için bu sayfa tartışmayı dakikalar içinde bitirir. Sayfayı yılda en az bir kez bir tatbikatla sınarsın. Sınanmamış bir oyun kitabı, gerçek olayda eksik adımlarını ancak iş işten geçtikten sonra gösterir.

Derinleş:Geçmişi yeniden yazmanın bedeli

Git geçmişini yeniden yazmak masum bir temizlik gibi görünür ama yan etkileri vardır. Her yeniden yazılan commit'in özeti değişir. İmzalı commit'lerin imzası bozulur ve commit özetine bağlı araçlar yolunu kaybeder. Açık çekme istekleri kötü etkilenir, inceleme yorumları kaybolabilir.

En büyük risk yeniden bulaşmadır. Ekipteki biri eski yerel kopyasından yeniden gönderim yaparsa temizlenen sır depoya geri döner. Bu yüzden temizlikten önce ekiple eşgüdüm kurulur ve herkes depoyu yeniden klonlar. GitHub bu iş için git-filter-repo aracının sır temizliğine özel bayrağını önerir.

Bütün bu zahmet, sır önceden iptal edildiyse çoğu zaman gereksizdir. İptal edilmiş bir anahtar geçmişte dursa bile bir kapı açmaz. Bu yüzden birçok ekip yalnız iptal eder ve geçmişi olduğu gibi bırakır.

Etkinlik 4 / 7

Bir anahtar yanlışlıkla depoya gönderildi ve bir sonraki commit'te silindi. Doğru müdahale nedir?

Tarama ve derleme sırları

Tarama iki katmanda kurulur. Birincisi gönderim öncesi kancadır (pre-commit hook). gitleaks ya da git-secrets gibi araçlar sırrı depoya girmeden, geliştiricinin makinesinde yakalar. Ama kanca atlanabilir, bu yüzden ikinci katman sunucu tarafındadır.

GitHub'ın itme koruması (push protection), bilinen biçimdeki sırları gönderim anında engeller. Kişisel hesaplarda herkese açık depolara itmede varsayılan olarak açıktır, depo düzeyinde ise yöneticinin açması gerekir.

Engeli aşan geliştirici listeden bir gerekçe seçer, olay denetim kaydına düşer ve depoyu izleyen yöneticilere e-posta gider. Boru hattındaki trufflehog ya da gitleaks taraması bütün geçmişi de kapsar. İmaj tarafında trivy'nin sır tarayıcısı varsayılan olarak açıktır ve imajı dağıtımdan önce bir kapıda durdurur.

Derleme sırrının çözümü, sırrı katmana hiç yazmamaktır. BuildKit'in sır bağlama özelliği (RUN --mount=type=secret), sırrı yalnız o RUN komutu süresince bir dosya olarak sunar. Varsayılan yer /run/secrets altıdır ve komut bitince sır kaybolur. Katmana da geçmişe de yazılmaz.

Sırrı derleme komutunda --secret id=npm,src=$HOME/.npmrc gibi verirsin. İstersen env= seçeneğiyle ortam değişkeni olarak da bağlarsın. Çok aşamalı derleme (multi-stage build) ikinci bir güvence ekler.

Son aşama temiz bir tabandan başlar ve yalnız açıkça kopyalanan dosyaları taşır. Derleme aşamasının katmanları son imaja girmez. Çalışma zamanı sırları ise imaja hiç girmez; uygulama onları çalışırken okur.

# Kötü: sır katmana ve geçmişe gömülür
ARG NPM_TOKEN=abc123
RUN npm ci

# İyi: çok aşamalı derleme, sır yalnız RUN süresince bağlanır
FROM node:22 AS derleme
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npm,target=/root/.npmrc npm ci
COPY . .
FROM gcr.io/distroless/nodejs22-debian13
COPY --from=derleme --chown=10001:10001 /app /app
USER 10001
CMD ["/app/sunucu.js"]

# Derleme komutu: sır yerel dosyadan okunur, imaja yazılmaz
$ docker build --secret id=npm,src=$HOME/.npmrc -t lab/uygulama:son .
Derleme aşamasının katmanları son imaja girmez; yalnız kopyalanan dosyalar taşınır.

Etkinlik 5 / 7

Yerelde duran uygulama:son imajını trivy ile sır taramasından geçir.

ogrenci@lab

Etkinlik 6 / 7

Doğru mu, yanlış mı?

Çok aşamalı derlemede son aşamaya kopyalanmayan derleme sırrı, üretilen imajda kalmaz.

Sırsız mimari ve süreç düzeltmesi

Kalıcı çözüm, sırrı koddan ve imajdan tümden çıkarmaktır. Uygulama sırrı çalışma anında kendi rolüyle sır yöneticisinden okur. Ama bu yol da denetlenir.

Sır yöneticisinden okuma yetkisi geniş verilirse sırrı imajdan çıkarmak yalnız yerini değiştirmiş olur. Doğru kurulum her iş yüküne yalnız kendi sırrını açan dar bir politika tanımlar ve okuma olaylarını kaydeder. Daha iyisi, sırrın hiç olmamasıdır.

Bulutta çalışan bir iş yükü kalıcı anahtar yerine rolünün geçici kimliğini kullanır. Boru hattı OIDC ile kısa ömürlü kimlik alır. Geliştiriciye yerelde kısa ömürlü kimlik veren bir yol açmak da kopyalanan kalıcı anahtarları ortadan kaldırır.

MITRE ATT&CK bu davranışı T1552 (Unsecured Credentials) altında, dosyalardaki kimlik bilgilerini T1552.001 (Credentials In Files) olarak tanımlar. Savunma tarafında sızıntıyı ölçmek mümkündür. Depolardaki bulgu sayısı, ortalama iptal süresi ve tekrar eden ekipler izlenir.

Sayılar düşmüyorsa sorun bireysel dikkat değil süreçtir. Geliştiriciye kolay bir alternatif verilmedikçe sır kopyalanmayı sürdürür. Taramayı arşivlenmiş depolara da uygularsın.

Aktif depolar genelde kapsamdadır, eski depolar unutulur. Oysa arşivdeki bir anahtar da hâlâ geçerli olabilir. GitGuardian'ın raporuna göre 2022'de sızan sırların yüzde 70'i rapor yayımlandığında hâlâ geçerliydi.

Derinleş:Vaka: Codecov Bash Uploader

Nisan 2021'de Codecov, Bash Uploader betiğinin değiştirildiğini duyurdu. Saldırgan, şirketin Docker imajı oluşturma sürecindeki bir hatadan yararlanmıştı. Bu hata, betiği değiştirmeye yetecek bir kimlik bilgisinin çıkarılmasına yol açtı. Yetkisiz erişim 31 Ocak 2021'de başladı ve 1 Nisan'da fark edildi.

Değiştirilen betik, müşterilerin CI ortamında çalışırken ortam değişkenlerini dışarıdaki bir sunucuya gönderdi. Böylece boru hattından geçen jetonlar, anahtarlar ve parolalar sızdı. Codecov'un önerisi de aynı sırayı izler.

CI'da env komutuyla açıkta kalan değişkenleri çıkar, hepsini hemen geçersiz kıl ve yenile, sonra bu kimliklerin nerede kullanıldığını denetle. Vaka iki dersi birlikte taşır. İmaj sürecinde açığa çıkan bir sır tedarik zincirini açar, boru hattındaki uzun ömürlü sırlar da hasarı büyütür.

Etkinlik 7 / 7

Uygulamanın veritabanı parolasını imaja gömmek yerine nasıl alması gerektiğini iki cümleyle yaz.

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