Ders 15 / 27
İçerik Güvenlik Politikası
Tarayıcıya hangi kaynağın yüklenebileceğini bildiren politikanın yazımı; yönergeler ve kaynak ifadeleri, im ve karma tabanlı izin, rapor kipi ve ihlal kaydı, politikanın kapsamadığı alanlar.
İçindekiler
Önceki ders savunmayı çıkışa yerleştirdi: her yazma noktasında bağlama uygun kaçırma. Bu savunmanın zayıf yanı kapsamıdır. Doğru olması için uygulamadaki her yolun doğru olması gerekir; tek bir ham işaretleme noktası, tek bir üçüncü taraf bileşen, tek bir eski sayfa şablonu yeter. Büyük bir arayüzde bu kapsamın sürekli korunacağını varsaymak, denetlenmesi zor bir varsayımdır.
Bu ders ikinci bir katman kurar. Katmanın niteliği önemlidir: uygulamanın kendi kodunun doğruluğuna bağlı değildir. Karar tarayıcıya bildirilir ve tarayıcı, belge ne söylerse söylesin, yükleme anında bu kararı uygular. Kuzey Yamaç Ölçüm İstasyonu arayüzü boyunca kurulacak politika, ölçüm notunu gösteren sayfanın hangi betikleri çalıştırabileceğini ve gömülü harita çerçevesine izin verilip verilmeyeceğini bu yolla sabitler.
Politikanın Yaptığı İş
İçerik güvenlik politikası (content security policy), bir yanıtın
Content-Security-Policy başlığıyla taşınan bildirimidir. Bildirim, belgenin hangi
türden kaynağı nereden yükleyebileceğini ve satır içi kodun hangi koşulla
çalıştırılabileceğini sayar. Tarayıcı bir alt kaynağı (subresource) getirmeden veya bir
betiği çalıştırmadan önce politikaya bakar; ifade listesinde eşleşme yoksa işlemi
gerçekleştirmez.
Bu, kaçırmanın yerine geçen bir savunma değildir. Kaçırma verinin koda dönüşmesini engeller; politika, engelin aşıldığı durumda çalışacak kodun ne yapabileceğini daraltır. İkisi bağımsız katmanlardır ve bağımsız oldukları için birlikte çalışırlar.
Politika HTTP başlığı olarak gönderilir. Belge içindeki meta ögesiyle de bildirilebilir,
ancak bu yol bazı yönergeleri desteklemez ve belge ayrıştırılmaya başladıktan sonra devreye
girer. Sunucu yapılandırmasında başlık olarak tanımlamak, kapsamı geniş olan yoldur.
Yönergeler ve Kaynak İfadeleri
Politika, noktalı virgülle ayrılmış yönergelerden (directive) oluşur. Her yönerge bir kaynak türünü adlandırır ve arkasından o tür için geçerli kaynak ifadelerini listeler.
default-src diğer yönergelerin yedeğidir: politika belirli bir tür için yönerge
içermiyorsa tarayıcı bu listeye bakar. script-src betikleri, style-src biçem
dosyalarını, img-src görselleri, connect-src betikten açılan ağ bağlantılarını,
font-src yazı tiplerini kapsar. Bunların yanında yükleme dışı kararlar veren yönergeler
vardır: frame-ancestors belgeyi kimin çerçeveleyebileceğini, form-action form
gönderiminin nereye gidebileceğini, base-uri belge taban adresinin değiştirilip
değiştirilemeyeceğini belirler.
Kaynak ifadeleri birkaç biçimdedir. 'none' hiçbir şeye izin vermez. 'self' belgenin
kendi kaynağını gösterir. Şema ifadesi (https:, data:) o şemadan gelen her adresi
kapsar. Konak ifadesi bir alan adını, önüne yıldız konarak alt alanlarını da içerir.
'nonce-…' ve 'sha256-…' satır içi kod için tek tek izin verir. 'unsafe-inline'
satır içi kodun tamamına izin verir ve adındaki uyarı yerindedir: bu ifade, politikanın
betik çalıştırmaya karşı sağladığı korumayı büyük ölçüde geçersiz kılar.
Politikayı Karara Bağlamak
Politikanın nasıl okunduğunu görmenin en doğrudan yolu, okuyan kodu yazmaktır. Aşağıdaki çözümleyici bir politika dizgesini yönergelere ayırır, verilen isteği ilgili listeye karşı sınar ve yedek yönergeye düşme davranışını uygular.
// csp.mjs — politika dizgesini cozumler ve bir kaynagin izinli olup olmadigini karara baglar function politikayiCozumle(dizge) { const yonergeler = new Map(); for (const parca of dizge.split(';')) { const sozcukler = parca.trim().split(/\s+/).filter(Boolean); if (sozcukler.length === 0) continue; yonergeler.set(sozcukler[0].toLowerCase(), sozcukler.slice(1)); } return yonergeler; } function konakEsler(ifade, adres) { const [semaKismi, kalan] = ifade.includes('://') ? ifade.split('://') : [null, ifade]; const [konak, ...yol] = kalan.split('/'); if (semaKismi && `${semaKismi}:` !== adres.protocol) return false; if (konak.startsWith('*.')) { if (!adres.hostname.endsWith(konak.slice(1))) return false; } else if (konak !== adres.host && konak !== adres.hostname) { return false; } if (yol.length > 0 && yol[0] !== '') return adres.pathname.startsWith('/' + yol.join('/')); return true; } function ifadeEsler(ifade, istek, belgeKaynagi) { if (ifade === "'none'") return false; if (ifade === "'self'") return istek.adres !== null && new URL(istek.adres).origin === belgeKaynagi; if (ifade === "'unsafe-inline'") return istek.satirIci === true && !istek.nonce; if (ifade.startsWith("'nonce-")) return istek.nonce === ifade.slice(7, -1); if (ifade.startsWith("'sha256-")) return istek.karma === ifade.slice(1, -1); if (ifade.endsWith(':')) return istek.adres !== null && new URL(istek.adres).protocol === ifade; return istek.adres !== null && konakEsler(ifade, new URL(istek.adres)); } function karar(politika, istek, belgeKaynagi) { const yonergeler = politikayiCozumle(politika); const ad = yonergeler.has(istek.yonerge) ? istek.yonerge : 'default-src'; const liste = yonergeler.get(ad); if (!liste) return { izinli: true, yonerge: null }; const eslesen = liste.find((ifade) => ifadeEsler(ifade, istek, belgeKaynagi)); return { izinli: Boolean(eslesen), yonerge: ad, eslesen: eslesen ?? null }; } function raporKaydi(istek, sonuc, politika, kip) { return { 'document-uri': 'https://olcum.ornek.test/istasyon/kuzey-yamac', 'violated-directive': sonuc.yonerge, 'blocked-uri': istek.adres ?? 'inline', 'disposition': kip, 'original-policy': politika, }; } const POLITIKA = "default-src 'none'; script-src 'self' 'nonce-r4k2' https://olcum-cdn.ornek.test; " + "img-src 'self' data:; connect-src 'self' https://*.veri.ornek.test; frame-ancestors 'none'"; const KAYNAK = 'https://olcum.ornek.test'; const istekler = [ { ad: 'kendi betigi', yonerge: 'script-src', adres: 'https://olcum.ornek.test/js/olcum.js' }, { ad: 'dagitim betigi', yonerge: 'script-src', adres: 'https://olcum-cdn.ornek.test/grafik.js' }, { ad: 'yabanci betik', yonerge: 'script-src', adres: 'https://baska.ornek.test/izleme.js' }, { ad: 'imli satir ici', yonerge: 'script-src', adres: null, satirIci: true, nonce: 'r4k2' }, { ad: 'imsiz satir ici', yonerge: 'script-src', adres: null, satirIci: true }, { ad: 'gomulu veri gorseli', yonerge: 'img-src', adres: 'data:image/png;base64,iVBOR' }, { ad: 'alt alan cagrisi', yonerge: 'connect-src', adres: 'https://olcum1.veri.ornek.test/kayit' }, { ad: 'yazi tipi', yonerge: 'font-src', adres: 'https://olcum.ornek.test/f/a.woff2' }, ]; for (const istek of istekler) { const sonuc = karar(POLITIKA, istek, KAYNAK); const satir = [ (sonuc.izinli ? 'izin' : 'engel').padEnd(6), istek.ad.padEnd(20), (sonuc.yonerge ?? '-').padEnd(14), sonuc.eslesen ?? '', ].join(' '); console.log(satir.trimEnd()); } console.log('\nrapor kaydi:'); const engellenen = istekler[2]; console.log(JSON.stringify(raporKaydi(engellenen, karar(POLITIKA, engellenen, KAYNAK), POLITIKA, 'enforce'), null, 2));
$ node csp.mjs
izin kendi betigi script-src 'self'
izin dagitim betigi script-src https://olcum-cdn.ornek.test
engel yabanci betik script-src
izin imli satir ici script-src 'nonce-r4k2'
engel imsiz satir ici script-src
izin gomulu veri gorseli img-src data:
izin alt alan cagrisi connect-src https://*.veri.ornek.test
engel yazi tipi default-src
rapor kaydi:
{
"document-uri": "https://olcum.ornek.test/istasyon/kuzey-yamac",
"violated-directive": "script-src",
"blocked-uri": "https://baska.ornek.test/izleme.js",
"disposition": "enforce",
"original-policy": "default-src 'none'; script-src 'self' 'nonce-r4k2' https://olcum-cdn.ornek.test; img-src 'self' data:; connect-src 'self' https://*.veri.ornek.test; frame-ancestors 'none'"
}
Son satır politikanın en öğretici davranışını gösterir. Yazı tipi isteği font-src
yönergesi bulunmadığı için default-src 'none' listesine düşer ve engellenir. Bu, sıkı
politika yazmanın önerilen biçimidir: default-src 'none' ile başlanır, uygulamanın
gerçekten kullandığı her tür için yönerge açıkça eklenir. Eksik yönerge sessizce izin
vermek yerine görünür bir hataya dönüşür.
İm ve Karma ile Satır İçi Kod
Satır içi betik gerçekten gerekiyorsa iki yol vardır ve ikisi de 'unsafe-inline'
kullanmaz.
İm (nonce), sunucunun her yanıt için ürettiği tahmin edilemez bir değerdir. Politikada
'nonce-r4k2' biçiminde bildirilir, betik ögesinde aynı değer nonce özniteliği olarak
yazılır. Tarayıcı yalnız eşleşen ögeyi çalıştırır. Üç kural bağlayıcıdır: değer kriptografik
kalitede rastgele üretilir, her yanıtta yenilenir ve önbelleğe alınan bir sayfayla birlikte
yeniden kullanılmaz. Sabit bir im, politikayı 'unsafe-inline' düzeyine indirir.
Karma tabanlı izin, içeriği değişmeyen satır içi betikler için uygundur. Betiğin
gövdesinin özeti alınır ve politikaya 'sha256-…' biçiminde yazılır. Yanıt başına üretim
gerektirmez, ama betik metni bir karakter değiştiğinde politika da güncellenmelidir; bu da
karmanın derleme aşamasında hesaplanmasını gerektirir.
Konak listeleri büyüdükçe politikanın koruma değeri düşer, çünkü listedeki her alan adı
güvenilen bir yükleme noktası hâline gelir. Bu sorunu azaltmak için tanımlanmış
'strict-dynamic' ifadesi, imle çalıştırılmış bir betiğin yüklediği alt betiklere güveni
devreder ve konak listesini gereksizleştirir. Bu ifadeyi anlamayan bir gerçekleştirimde
politikanın çalışmaya devam etmesi için, ifade konak listesiyle birlikte yazılır: eski
davranış listeyi okur, yeni davranış listeyi yok sayar.
Rapor Kipi ve Yayına Alma
Sıkı bir politikayı hazır bir uygulamaya doğrudan uygulamak, çalışan işlevleri kırar.
Content-Security-Policy-Report-Only başlığı bu yüzden vardır: tarayıcı politikayı
değerlendirir, ihlalleri bildirir, ama hiçbir şeyi engellemez. Yukarıdaki rapor kaydında
disposition alanı bu ayrımı taşır — zorlama kipinde enforce, rapor kipinde report
değerini alır.
İhlal kayıtları, politikada bildirilen bir toplama ucuna gönderilir. Kaydın alanları
ihlalin hangi belgede oluştuğunu, hangi yönergenin ihlal edildiğini ve neyin engellendiğini
söyler. Satır içi kod için blocked-uri alanı bir adres yerine inline değerini taşır.
Yayına alma sırası şudur: rapor kipiyle başlanır, gelen kayıtlar bir süre toplanır, meşru yükleme noktaları politikaya eklenir, kalan ihlaller kod düzeltmesiyle giderilir; ancak kayıt akışı durulduktan sonra zorlama kipine geçilir. Toplama ucunun kendisi de gürültüye dayanıklı olmalıdır: kullanıcı tarafında sayfaya kod ekleyen eklentiler, uygulamayla ilgisi olmayan ihlaller üretir ve bu kayıtlar ayıklanmadığında gerçek bulgular görünmez olur.
Politikanın Kapsamadığı Alan
Politika yükleme ve çalıştırma kararlarını denetler; belge içinde çalışan meşru kodun ne yaptığını denetlemez. Önceki derste tanıtılan DOM tabanlı sınıf bu boşluğa düşer: uygulamanın kendi betiği, kendi kaynağından gelen bir değeri ham işaretleme olarak yazarsa politika bunu bir dış yükleme saymaz.
Bu boşluğu daraltmak için tanımlanmış olan mekanizma, ham işaretleme kabul eden yerleştirme noktalarını yalnız denetlenmiş bir tür üzerinden yazılabilir kılar ve dizgi atamasını hata hâline getirir. Politikaya bir yönergeyle bağlanır ve rapor kipiyle ölçülebilir. Desteği her ortamda bulunmayabileceği için, yetenek saptamayla sınanır ve bulunmadığında uygulama önceki derste kurulan kaçırma katmanına dayanır.
Özet
- İçerik güvenlik politikası, hangi kaynağın yüklenip hangi kodun çalıştırılabileceğini tarayıcıya bildirir ve kararı uygulamanın kendi kodundan bağımsız kılar.
- Politika yönergelerden oluşur;
default-srcdiğerlerinin yedeğidir, bu yüzdendefault-src 'none'ile başlayıp gerekli türleri tek tek eklemek eksikleri görünür kılar. - Satır içi kod için
'unsafe-inline'yerine yanıt başına yenilenen im veya derleme aşamasında hesaplanan karma kullanılır. - Rapor kipi politikayı engellemeden değerlendirir; yayına alma rapor kipiyle başlar, kayıt akışı durulduktan sonra zorlama kipine geçilir.
- Politika dış yükleme kararlarını denetler; belgenin kendi kodunun ham işaretleme yazması ayrı bir mekanizmayla sınırlandırılır.
Sonraki Adım
Politika, betiğin nereden yüklenebileceğini söyler. Betik yüklendikten sonra veri okumak için başka bir kaynağa istek attığında ise başka bir kural kümesi devreye girer: aynı kaynak politikası, bu isteğin yanıtının okunmasına öntanımlı olarak izin vermez. Ölçüm arayüzünün ayrı bir alan adındaki veri ucundan okuma yapabilmesi için sunucunun açık bir izin bildirmesi gerekir. Sonraki ders bu izin mekanizmasını, tarayıcının uyguladığı kısıtları ve doğru sunucu yapılandırmasını kurar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.