İçeriğe geç
academia.sh

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-src diğerlerinin yedeğidir, bu yüzden default-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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat