Güvenli varsayılanlar ve en az yetki
- Güvenli varsayılanı, en az yetkiyi veritabanında, konteynerde ve kodda uygulamayı, güvenli kapanmayı ve süreli yetki yükseltmeyi öğrenirsin.
- En az yetki, her hesabın ve servisin yalnız işi için gereken hakka sahip olmasıdır.
- Bu derste AltayKargo iade servisinin ayar dosyasında bu iki ilkeyi sınayacaksın.
Puan bütün etkinlikleri cevaplayınca hesaba yazılır. Bu dersi tamamlayınca +50 XP alacaksın. Giriş yapmadan ilerlersen puan yalnız bu tarayıcıda durur.
Bir sistemin ilk hali, kullanıcı hiçbir ayarla oynamadan durduğu halidir. Bu ilk hal güvensizse, kullanıcının bunu fark edip düzeltmesini beklemiş olursun; çoğu kullanıcı ayar sayfasını hiç açmaz. Güvenli varsayılan ilkesi bunu tersine çevirir: en riskli seçenek kapalı başlar, kullanıcı isterse bilerek açar.
AltayKargo iade servisinde bir örnek düşün: yeni oluşturulan her iade kaydı için otomatik olarak herkese açık bir paylaşım linki üretilsin mi? Doğru varsayılan hayırdır. Link isteyen müşteri, kendisi bir düğmeye basıp isteyerek açar.
Varsayılan açıksa, hiç fark etmeden herkese açık kalan kayıtlar birikir. Varsayılanı güvenli tutmak kullanıcıdan ekstra bir işlem istemez; tam tersine, riskli olanı açmak için ekstra bir adım ister.
Varsayılan, ürünün sorumluluğudur
Güvenli tasarım (secure by design) ile güvenli varsayılan (secure by default) yakın ama farklı fikirlerdir. Güvenli tasarım, ürünün baştan saldırıya dayanacak biçimde kurulmasıdır. Güvenli varsayılan ise ürünün kutudan çıktığı haliyle, hiçbir ayar yapılmadan güvenli çalışmasıdır. ABD siber güvenlik ajansı CISA, 2023'te yayımladığı Secure by Design rehberinde üç ilke koyar: müşterinin güvenlik sonucunu sahiplenmek, radikal şeffaflık ve hesap verebilirlik, güvenliği üst yönetimin sahiplenmesi.
Birinci ilke bu dersin özüdür. Güvenlik yükünü kullanıcıya yıkan bir varsayılan, sorunu ürünün dışına iter. Kullanıcı ayar sayfasını açmadıysa hata onun değil, varsayılanı seçen ekibindir.
Derinleş:Saltzer ve Schroeder'in sekiz ilkesi
Güvenli varsayılan ve en az yetki yeni fikirler değildir. Jerome Saltzer ile Michael Schroeder, 1975'te Proceedings of the IEEE dergisinde yayımlanan The Protection of Information in Computer Systems makalesinde sekiz tasarım ilkesi sıraladı. Listedeki iki ilke bu dersin konusudur. Hata güvenli varsayılanlar (fail-safe defaults), erişim kararını dışlamaya değil izne dayandırır; varsayılan durum erişimin olmamasıdır.
En az yetki (least privilege), her programın ve her kullanıcının işini bitirmeye yetecek en küçük yetki kümesiyle çalışmasını ister. Öteki ilkeler de bugün tanıdık gelir. Tam aracılık (complete mediation) her erişimin denetlenmesini söyler. Açık tasarım (open design) güvenliğin tasarımın gizli kalmasına dayanmamasını ister.
Yetki ayrımı (separation of privilege) kritik bir işi birden çok koşula bağlar. Mekanizma ekonomisi, en az ortak mekanizma ve psikolojik kabul edilebilirlik listeyi tamamlar. Yarım asır sonra bu liste güvenli tasarım derslerinin hâlâ başlangıç noktasıdır.
Güvensiz varsayılanlar birkaç tanıdık kalıpta toplanır. Her kurulumda aynı olan admin/admin gibi bir parolanın güvenli karşılığı, ilk kurulumda parolayı zorla değiştirtmek ya da her kuruluma ayrı parola üretmektir. Üretimde açık kalan hata ayıklama modu yığın izi ve yapılandırma sızdırır; kapalı başlar.
Her kaynağa izin veren joker CORS başlığı yerine izin listesi kullanılır. Herkese açık paylaşım bağlantısı ve dizin listeleme kapalı başlar. Yönetici hesabında çok adımlı doğrulama açık başlar.
CISA'nın Ürün Güvenliği Kötü Uygulamaları rehberi 17 Ocak 2025'te 2.0 sürümüne geçti. Rehber, kritik altyapıda kullanılan ürünlerde iki varsayılanı tehlikeli sayar: varsayılan parola ve yöneticide kendiliğinden açılmayan çok adımlı doğrulama. Bu zayıflığın CWE karşılığı CWE-1188'dir: bir kaynağı güvensiz bir varsayılanla başlatmak.
Bu modülün dört dersi de yaşam döngüsünün aynı basamağında durur: Tasarım. Mimariyi çizip tehdidi STRIDE'la bulmak, sınırı işaretlemek, kötüye kullanım senaryosu yazmak ve varsayılanla yetkiyi sıkılaştırmak, hepsi kod satırı yazılmadan önce olur. Bir sonraki modül Kodlama basamağına geçer: aynı kararları girdi, çıktı ve veri işlemenin içine döker. Bu sıralama rastgele değildir; tasarımda ucuza kapatılan bir karar, kodlamada çok daha pahalıya düzeltilir.
Etkinlik 1 / 7
Aşağıdaki iade servisi ayar dosyasında güvensiz varsayılanı taşıyan satırı bul.
Veritabanında en az yetki
Varsayılanı düzeltmek tek başına yetmez. İki ilke birbirini tamamlar: biri başlangıç durumunu, öteki günlük çalışma yetkisini sıkılaştırır. En az yetki farklı bir katmanda çalışır.
Her hesap, her servis ve her süreç yalnız işini yapmak için gereken hakka sahip olur, ne bir fazla ne bir eksik. AltayKargo'nun iade servisi hesabını düşün; bu hesabın günlük işi yalnız iadeler tablosuna okuma ve yazmadır. Aynı hesaba tablo silme yetkisi ya da müşteri tablosuna erişim vermek, işin gerektirmediği bir güç ekler.
İlkeyi PostgreSQL'de somut olarak gör. Tabloyu şema değişikliklerini yapan iade_sahip rolü oluşturur ve tablonun sahibi odur. Uygulama ayrı bir rolle bağlanır: iade_svc. Yönetici oturumunda bu role yalnız üç hak verilir, sonra psql'in \dp komutuyla tablonun erişim listesine bakılır.
-- yönetici (süper kullanıcı) oturumunda
CREATE ROLE iade_svc LOGIN;
GRANT SELECT, INSERT, UPDATE ON iadeler TO iade_svc;
\dp iadeler
Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies
--------+---------+-------+--------------------------------+-------------------+----------
public | iadeler | table | iade_sahip=arwdDxtm/iade_sahip+| |
| | | iade_svc=arw/iade_sahip | |
(1 row)
-- uygulama rolüne geç ve sınırı dene
SET ROLE iade_svc;
DELETE FROM iadeler WHERE id = 42;
ERROR: permission denied for table iadeler
SELECT * FROM musteriler;
ERROR: permission denied for table musterilerÇıktıyı satır satır oku. Access privileges sütunundaki her satır rol=haklar/veren biçimindedir. iade_sahip=arwdDxtm satırı sahibin bütün tablo haklarını gösterir; satır sonundaki artı işareti hücrenin alt satırda sürdüğünü anlatır.
iade_svc=arw/iade_sahip satırı uygulama rolünündür. a INSERT, r SELECT, w UPDATE demektir. d (DELETE), D (TRUNCATE), x (REFERENCES), t (TRIGGER) ve m (MAINTAIN) bu satırda yoktur.
m harfi PostgreSQL 17 ile geldi; eski sürümlerde sahibin satırı arwdDxt ile biter. \dp komutunun kısa eşi \z'dir. Son iki komut sınırı dener: uygulama rolü kayıt silemez, müşteri tablosunu da okuyamaz. İade servisinde bir SQL enjeksiyonu çıksa bile saldırgan bu rolün hakları kadar ilerler.
Etkinlik 2 / 7
psql'de iadeler tablosunun erişim listesini görüntüle.
Aynı soru veritabanının dışında da geçerlidir. Rapor görevi, yedekleme görevi ya da destek paneli için de sorarsın: bu hesabın günlük işi hangi hakları gerektiriyor? Listenin dışında kalan her hak, saldırganın o hesabı ele geçirdiği gün kullanacağı fazladan güçtür.
Hakları kapsamla da daraltırsın. Tablo yerine salt okunur bir görünüm, bütün kayıtlar yerine tek bir bölge, bütün klasör yerine tek bir alt klasör verilir.
Etkinlik 3 / 7
Her servis hesabını sahip olması gereken en dar yetkiyle eşleştir.
Süreç ve konteyner kök olmadan çalışır
Kök kullanıcı (root) olarak çalışan bir süreç, en az yetkinin tam tersidir. Web uygulamasında bir kod çalıştırma açığı çıkarsa saldırgan sürecin kimliğini devralır; süreç kökse saldırgan da köktür. CWE-250 bu zayıflığı tanımlar: gereğinden fazla yetkiyle çalışmak.
Konteynerde iki katmanda önlem alırsın. İlki imajdır. Dockerfile'daki USER satırı süreci sıradan bir kullanıcıyla başlatır.
Kullanıcıyı ad yerine 10001 gibi bir sayıyla yazmak iyi bir alışkanlıktır. Kubernetes kullanıcının kök olmadığını yalnız sayısal kimlikte denetleyebilir. İmajda kullanıcı adı yazılıysa ve pod tanımı sayısal bir runAsUser vermiyorsa, runAsNonRoot denetimi konteyneri başlatmaz. İkinci katman çalışma zamanıdır: securityContext alanı, imaj ne derse desin kuralları zorlar.
# Dockerfile
FROM python:3.13-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
USER 10001
CMD ["python", "-m", "iade"]
# Kubernetes pod tanımında konteynerin securityContext alanı
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefaultSatırları oku. runAsNonRoot: true, süreç kök kimlikle başlayacaksa konteyneri hiç başlatmaz. allowPrivilegeEscalation: false, sürecin setuid bitli bir programla yetkisini artırmasını engeller.
readOnlyRootFilesystem: true, kök dosya sistemini salt okunur bağlar; saldırgan diske araç bırakamaz. Uygulama bir yere yazacaksa ayrı bir geçici birim tanımlanır. capabilities altındaki drop satırı, çekirdeğin kök gücünü böldüğü yeteneklerin hepsini kaldırır.
seccompProfile: RuntimeDefault, çalışma ortamının varsayılan sistem çağrısı filtresini açar. Bu satırlar tek başına bir açığı kapatmaz. Açık çıktığında saldırganın o konteynerden ne kadar ileri gidebileceğini daraltır.
Derinleş:Linux yetenekleri ve kökün parçaları
Linux'ta kök kullanıcının gücü tek parça değildir. Çekirdek bu gücü yetenek (capability) adı verilen birimlere böler. CAP_NET_BIND_SERVICE 1024'ün altındaki portlara bağlanmayı, CAP_CHOWN dosya sahibini değiştirmeyi açar. CAP_SYS_ADMIN ise öyle geniş bir yönetim işleri kümesini kapsar ki çoğu zaman yeni kök diye anılır.
Konteyner çalışma ortamları süreci küçültülmüş bir yetenek setiyle başlatır, ama bu set yine de uygulamanın hiç kullanmadığı yetenekler taşır. drop listesine ALL yazmak hepsini kaldırır. Uygulama gerçekten birine ihtiyaç duyuyorsa, örneğin 80 numaralı porta bağlanacaksa, yalnız o yetenek add listesine yazılır.
Daha iyi yol, uygulamayı 8080 gibi yüksek bir portta çalıştırıp yeteneği hiç istememektir. Yetenekler dosyalara da bağlanabilir. setcap ile yetenek verilmiş bir program, onu çalıştıran sıradan kullanıcıya bu gücü taşır. allowPrivilegeEscalation: false bu tür kazanımları da keser.
Etkinlik 4 / 7
İade servisinin securityContext alanında en az yetki ilkesini bozan satırı bul.
Kodda varsayılan ret
Uygulama katmanında en az yetki, varsayılan ret (deny by default) olarak görünür. OWASP'ın yetkilendirme rehberi iki ilkeyi öne çıkarır. Birincisi, açıkça izinli olmayan her istek reddedilir.
İkincisi, yetki her istekte yeniden denetlenir; önceki bir sayfadaki denetime güvenilmez. OWASP Top 10:2025'in ilk sırasındaki Bozuk Erişim Denetimi (Broken Access Control) bu ilkelerin çiğnenmesinden doğar. Aşağıdaki işlev her uç nokta için izinli rolleri bir tabloda tutar.
Müşteri, kurye, destek ve yönetici rolleri ayrı haklar taşır; her uç nokta yalnız oturumu değil, doğru rolü de denetler. Tabloda olmayan rota reddedilir, müşteri rolünde nesne sahipliği de denetlenir. Büyük sistemlerde bu tablo merkezi bir politika motoruna taşınır. Her rol ve rota çifti için yazılan bir yetki testi de tabloya eklenen yeni rotanın unutulmasını önler.
IZINLER = {
("GET", "/iadeler/{id}"): {"musteri", "destek", "yonetici"},
("POST", "/iadeler/{id}/onay"): {"yonetici"},
}
def yetkili_mi(kullanici, yontem, rota, iade):
roller = IZINLER.get((yontem, rota))
if roller is None: # tabloda yok: varsayılan ret
return False
if kullanici.rol not in roller:
return False
if kullanici.rol == "musteri" and iade.musteri_id != kullanici.id:
return False # nesne sahipliği
return Trueİşlevi yukarıdan aşağı izle. IZINLER sözlüğü her yöntem ve rota çiftini izinli roller kümesine bağlar. İşlev önce isteğin çiftini sözlükte arar.
get yöntemi bulamadığı anahtar için hata fırlatmaz, None döner ve işlev hemen False verir; tabloya yazılmamış bir rota kimseye açılmaz. Sonra rolün kümede olup olmadığına bakılır. Son denetim nesne sahipliğidir: müşteri, başka bir müşterinin iadesini kimlik numarasını bilse bile göremez.
ASVS 5.0 bu denetimi veriye özgü erişim denetimi olarak adlandırır ve eksikliğini IDOR ile BOLA açıklarına bağlar. Bu satır yoksa saldırgan adresteki numarayı değiştirip başkasının kaydını okur.
Etkinlik 5 / 7
Destek rolündeki bir kullanıcı POST /iadeler/{id}/iptal isteği gönderiyor. yetkili_mi işlevi bu istek için ne yapar?
Önce kodu oku, sonra çıktıyı seç.
IZINLER = {
("GET", "/iadeler/{id}"): {"musteri", "destek", "yonetici"},
("POST", "/iadeler/{id}/onay"): {"yonetici"},
}
def yetkili_mi(kullanici, yontem, rota, iade):
roller = IZINLER.get((yontem, rota))
if roller is None: # tabloda yok: varsayılan ret
return False
if kullanici.rol not in roller:
return False
if kullanici.rol == "musteri" and iade.musteri_id != kullanici.id:
return False # nesne sahipliği
return True
# istek: kullanici.rol = "destek", yontem = "POST", rota = "/iadeler/{id}/iptal"Güvenli kapanma ve süreli yetki
Güvenli kapanma (fail closed) aynı fikrin hata anındaki halidir: bir şey ters giderse sistem güvenli tarafa düşer. OWASP Top 10:2025'e yeni giren A10 kategorisi, İstisnai Durumların Yanlış Ele Alınması (Mishandling of Exceptional Conditions), bu konuyu işler. Yarım kalan bir işlemin bütün parçalarının geri alınmasını ister. CWE-636 tersini, yani açık kapanmayı (failing open) tanımlar.
En yaygın örnek başlangıç ayarıdır. JWT imza sırrı tanımlı değilse uygulama sessizce bir varsayılan sırla açılmamalıdır. Başlamayı reddeder ve nedenini yazar. Aynı kural yetki denetiminde de geçerlidir: yetki servisine ulaşılamazsa karar evet değil, hayırdır.
import os, sys
# Açık kapanma: sır yoksa herkesin tahmin edebileceği bir değerle çalışır
JWT_SIRRI = os.environ.get("JWT_SIRRI", "gizli")
# Güvenli kapanma: sır yoksa ya da kısaysa uygulama hiç başlamaz
JWT_SIRRI = os.environ.get("JWT_SIRRI")
if not JWT_SIRRI or len(JWT_SIRRI) < 32:
sys.exit("JWT_SIRRI tanımsız ya da kısa, uygulama başlatılmadı")İki yazımı karşılaştır. İlk satırda değişken tanımsız kalırsa get yöntemi ikinci argümanı döner ve uygulama gizli kelimesiyle imza atar. Bu kelimeyi tahmin eden herkes geçerli bir jeton üretir ve içine istediği rolü yazar.
İkinci blokta ikinci argüman yoktur; değişken tanımsızsa get None döner. Eksik ya da 32 karakterden kısa bir sır uygulamayı durdurur. Kesinti kötü görünür ama sessiz bir açıktan iyidir: dağıtım hemen başarısız olur, izleme alarmı çalar ve düzeltme dakikalar sürer.
Zaman sınırı da en az yetkinin bir parçasıdır. Bazı işler kısa süreliğine geniş yetki ister: üretimde bir veri düzeltmesi ya da acil bir olay müdahalesi. Kalıcı yönetici hesabı yerine tam zamanında (just-in-time) yükseltme kullanılır.
Kişi yetkiyi bir gerekçeyle ister, bir onaylayan kabul eder ve süre dolunca yetki kendiliğinden geri iner. Bu otomatik geri alma, insan hatasına bırakılan bir adımdan çok daha güvenilirdir. Acil durumlar için bir de cam kırma (break-glass) hesabı tutulur.
Parolası kasada durur, kullanımı alarm üretir ve her kullanımdan sonra parola değişir. Her yükseltme kim, ne zaman ve hangi gerekçeyle bilgisiyle denetim kaydına yazılır.
Etkinlik 6 / 7
Bir geliştirici, üretim veritabanına tablo silme yetkisi olan hesapla her sabah günlük rapor sorgusu çalıştırıyor. Bu hangi ilkeyi çiğniyor?
Güvenli varsayılan en riskli seçeneği kapalı başlatır, kullanıcı isterse bilerek açar. En az yetki her hesaba yalnız işinin gerektirdiği kadar hak verir. İkisi birlikte tasarım aşamasının son iki güvencesidir.
Bulut depolama kovası, paylaşım bağlantısı ya da yeni bir kullanıcı rolü fark etmez; hepsinde aynı soruyu sorarsın: en riskli seçenek başlangıçta açık mı? Bir sonraki modülde bu kararları koda döküp girdiyi, çıktıyı ve sorguyu güvenli yazacaksın.
Etkinlik 7 / 7
Doğru mu, yanlış mı?
Yeni oluşturulan bir bulut depolama kovasının varsayılan olarak herkese açık okunabilir olması güvenli varsayılan ilkesine uygundur.
Okumayı istediğin sırayla sürdürebilirsin. Ders, bütün etkinlikleri cevapladığında tamamlanır.