İçeriğe geç
academia.sh

Ders 17 / 27

İstek Sahteciliği

Çerezlerin kendiliğinden eklenmesinden doğan saldırı sınıfı; SameSite ve Secure niteliklerinin karar tablosu, oturuma bağlanmış istek belirteci, kaynak başlığı denetimi ve yöntem disiplini.

İçindekiler

Önceki ders bir soruyla bitti: aynı kaynak politikası yanıtın okunmasını engelliyorsa, yazma isteği ne oluyor? Yanıt, engellenmediğidir. Kaynaklar arası bir istek sunucuya ulaşır, sunucu isteği işler, yan etki gerçekleşir; yalnız yanıt gövdesi isteği başlatan sayfaya teslim edilmez.

Bu boşluk tek başına sorun değildir. Sorun, isteğe kullanıcının kimliğinin kendiliğinden eklenmesiyle oluşur. Kuzey Yamaç Ölçüm İstasyonu arayüzünde oturum bir çerezle taşınıyorsa, tarayıcı o çerezi isteğin nereden başlatıldığına bakmadan ekler. Bu ders o davranışın sonucunu ve iki bağımsız savunma katmanını kurar.

Ortam Yetkisi

Çerez, İnternet Nasıl Çalışır kursunda tanımlandığı gibi istekle birlikte kendiliğinden gider. Bu, oturumun sürmesini sağlayan davranıştır: kullanıcı her sayfada kimliğini yeniden bildirmez. Aynı davranış bir yan sonuç üretir. İsteği kimin başlattığı çerezin eklenmesini etkilemez; tarayıcı hedef adrese bakar, kaynağa değil.

Buna ortam yetkisi (ambient authority) denir: yetki, isteği yapan koda değil, isteğin gittiği yere iliştirilmiştir. Siteler arası istek sahteciliği (cross-site request forgery) bu yetkinin başka bir sitenin sayfasından tetiklenmesidir. Kullanıcı ölçüm istasyonuna giriş yapmış durumdayken başka bir sekmede bir sayfa açar; o sayfa, ölçüm istasyonunun bir uç adresine yazma isteği başlatır. İstek kullanıcının çerezini taşıdığı için sunucu tarafında meşru görünür.

Saldırıya betik gerekmez. Bir form ögesi gönderilebilir, bir görsel ögesi bir adresi çağırabilir. Yanıtın okunamaması saldırganın umurunda değildir; amaç okumak değil, yaptırmaktır.

Birinci Katman: Çerez Nitelikleri

İlk savunma çerezin kendisindedir. SameSite niteliği, çerezin kaynaklar arası isteklere eklenip eklenmeyeceğini belirler ve üç değer alır. Aşağıdaki kod bu değerlerin karar tablosunu hesaplar.

// samesite.mjs — cerez nitelikleri istek turlerine gore karar tablosu uretir
const GUVENLI_YONTEMLER = new Set(['GET', 'HEAD']);

function gonderilirMi(cerez, istek) {
  if (cerez.secure && istek.sema !== 'https') return ['hayir', 'Secure nitelikli cerez sifresiz semada gonderilmez'];
  if (istek.ayniSite) return ['evet', 'ayni site istegi'];
  switch (cerez.sameSite) {
    case 'Strict':
      return ['hayir', 'Strict kaynaklar arasi her istegi diser'];
    case 'Lax':
      return istek.ustDuzeyGezinme && GUVENLI_YONTEMLER.has(istek.yontem)
        ? ['evet', 'Lax ust duzey gezinmede guvenli yontemi gecirir']
        : ['hayir', 'Lax yalniz ust duzey guvenli gezinmeyi gecirir'];
    case 'None':
      return cerez.secure ? ['evet', 'None kaynaklar arasi gonderir'] : ['hayir', 'None icin Secure zorunlu'];
    default:
      return ['hayir', 'taninmayan deger'];
  }
}

const istekler = [
  { ad: 'ayni site cagri', ayniSite: true, ustDuzeyGezinme: false, yontem: 'GET', sema: 'https' },
  { ad: 'disaridan bag (GET)', ayniSite: false, ustDuzeyGezinme: true, yontem: 'GET', sema: 'https' },
  { ad: 'disaridan form (POST)', ayniSite: false, ustDuzeyGezinme: true, yontem: 'POST', sema: 'https' },
  { ad: 'disaridan cerceve', ayniSite: false, ustDuzeyGezinme: false, yontem: 'GET', sema: 'https' },
  { ad: 'disaridan betik cagrisi', ayniSite: false, ustDuzeyGezinme: false, yontem: 'POST', sema: 'https' },
];

const cerezler = [
  { ad: 'Strict+Secure', sameSite: 'Strict', secure: true },
  { ad: 'Lax+Secure', sameSite: 'Lax', secure: true },
  { ad: 'None+Secure', sameSite: 'None', secure: true },
  { ad: 'None, Secure yok', sameSite: 'None', secure: false },
];

const basliklar = ['istek turu'.padEnd(24), ...cerezler.map((c) => c.ad.padEnd(16))].join('').trimEnd();
console.log(basliklar);
for (const istek of istekler) {
  const hucreler = cerezler.map((cerez) => gonderilirMi(cerez, istek)[0].padEnd(16));
  console.log(istek.ad.padEnd(24) + hucreler.join('').trimEnd());
}

console.log('\ngerekce ornegi (Lax+Secure):');
for (const istek of istekler) {
  const [karar, neden] = gonderilirMi(cerezler[1], istek);
  console.log(' ', istek.ad.padEnd(24), karar.padEnd(6), neden);
}
$ node samesite.mjs
istek turu              Strict+Secure   Lax+Secure      None+Secure     None, Secure yok
ayni site cagri         evet            evet            evet            evet
disaridan bag (GET)     hayir           evet            evet            hayir
disaridan form (POST)   hayir           hayir           evet            hayir
disaridan cerceve       hayir           hayir           evet            hayir
disaridan betik cagrisi hayir           hayir           evet            hayir

gerekce ornegi (Lax+Secure):
  ayni site cagri          evet   ayni site istegi
  disaridan bag (GET)      evet   Lax ust duzey gezinmede guvenli yontemi gecirir
  disaridan form (POST)    hayir  Lax yalniz ust duzey guvenli gezinmeyi gecirir
  disaridan cerceve        hayir  Lax yalniz ust duzey guvenli gezinmeyi gecirir
  disaridan betik cagrisi  hayir  Lax yalniz ust duzey guvenli gezinmeyi gecirir

Tablo üç kuralı görünür kılar. Strict, kaynaklar arası her isteği diser; koruması en geniştir ama başka bir siteden gelen bir bağla giren kullanıcı oturumsuz karşılanır. Lax, yalnız üst düzey gezinmede ve yalnız güvenli yöntemle çerezi geçirir; dışarıdan gelen bir formun POST isteği ile bir çerçeve yüklemesi eler. None her durumda gönderir ve bu yüzden Secure niteliğini zorunlu kılar; nitelik yoksa çerez hiç gönderilmez.

Bu tabloyla birlikte iki nitelik daha oturum çerezinin ayrılmaz parçasıdır. HttpOnly, çerezin betikten okunmasını engeller; ilk derste kurulan betik çalıştırma savunması aşıldığında oturumun doğrudan çalınmasını zorlaştırır. Secure, çerezin şifresiz bir bağlantıda gönderilmesini önler.

SameSite’ın Yetmediği Yerler

Nitelik güçlü bir azaltmadır, ama tek savunma olarak yeterli sayılmaz.

Birincisi, “aynı site” ile “aynı kaynak” farklı ölçütlerdir. Aynı site ölçütü kayıt edilebilir alan adı üzerinden hesaplanır; rapor.ornek.test ile olcum.ornek.test ayrı kaynaklardır ama aynı sitedir. Alt alanlardan birinin denetimini kaybetmek, o alan üzerinden gönderilen isteklerin aynı site sayılması demektir. Bu riski daraltmak için çerez adına __Host- öneki konur: bu önek, çerezin yalnız tam olarak belirlenen konağa ait olmasını ve alan adı kapsamının genişletilememesini zorunlu kılar.

İkincisi, niteliğin bildirilmediği durumda uygulanacak davranış gerçekleştirime bağlıdır. Bazı gerçekleştirimler bildirilmemiş çerezi kısıtlayıcı bir değer gibi ele alır, bazıları almaz. Bu yüzden nitelik varsayılana bırakılmaz, açıkça yazılır.

Üçüncüsü, Lax değeri üst düzey GET gezinmesini geçirir. Durum değiştiren bir işlem GET ile yapılıyorsa bu savunma o işlemi kapsamaz. Yöntem disiplini bu yüzden savunmanın parçasıdır.

İkinci Katman: Oturuma Bağlı Belirteç

İkinci savunma, isteğin gerçekten uygulamanın kendi sayfasından başlatıldığını kanıtlar. Sunucu her oturum için tahmin edilemez bir istek belirteci üretir, sayfaya gömer ve gönderimde geri bekler. Belirtecin oturuma bağlanması kritiktir: bağlanmazsa saldırgan kendi oturumu için aldığı geçerli bir belirteci başka bir kullanıcının isteğine yerleştirebilir.

// belirtec.mjs — oturuma baglanmis istek belirteci uretimi ve dogrulanmasi
import { createHmac, timingSafeEqual } from 'node:crypto';

const SUNUCU_GIZLISI = Buffer.from('7b2a0f5c9d1e4a8b6c3f0d2e5a7b9c1d', 'utf8');

function belirtecUret(oturumKimligi, tuz) {
  const imza = createHmac('sha256', SUNUCU_GIZLISI).update(`${oturumKimligi}.${tuz}`).digest('base64url');
  return `${tuz}.${imza}`;
}

function belirtecDogrula(oturumKimligi, belirtec) {
  if (typeof belirtec !== 'string' || !belirtec.includes('.')) return false;
  const [tuz] = belirtec.split('.');
  const beklenen = Buffer.from(belirtecUret(oturumKimligi, tuz));
  const gelen = Buffer.from(belirtec);
  return beklenen.length === gelen.length && timingSafeEqual(beklenen, gelen);
}

function kaynakDenetimi(basliklar, beklenen) {
  if (basliklar['sec-fetch-site'] === 'same-origin') return true;
  if (basliklar.origin) return basliklar.origin === beklenen;
  return false;
}

const OTURUM = 'oturum-4417';
const gecerli = belirtecUret(OTURUM, 'a1b2c3');
console.log('uretilen belirtec:', gecerli);

const denemeler = [
  ['dogru oturum, dogru belirtec', OTURUM, gecerli],
  ['baska oturumun belirteci', 'oturum-9002', gecerli],
  ['imzasi degistirilmis', OTURUM, `${gecerli.slice(0, -1)}X`],
  ['belirtec yok', OTURUM, undefined],
];

console.log('\nbelirtec dogrulama:');
for (const [ad, oturum, deger] of denemeler) {
  console.log(' ', ad.padEnd(30), belirtecDogrula(oturum, deger) ? 'gecerli' : 'reddedildi');
}

const BEKLENEN_KAYNAK = 'https://olcum.ornek.test';
const baslikKumeleri = [
  ['kendi sayfasindan', { origin: BEKLENEN_KAYNAK, 'sec-fetch-site': 'same-origin' }],
  ['baslik yok, isaret var', { 'sec-fetch-site': 'same-origin' }],
  ['disaridan gonderim', { origin: 'https://baska.ornek.test', 'sec-fetch-site': 'cross-site' }],
  ['ikisi de yok', {}],
];

console.log('\nkaynak denetimi:');
for (const [ad, basliklar] of baslikKumeleri) {
  console.log(' ', ad.padEnd(30), kaynakDenetimi(basliklar, BEKLENEN_KAYNAK) ? 'kabul' : 'reddedildi');
}
$ node belirtec.mjs
uretilen belirtec: a1b2c3.F5jj3rvIaTbTm94oBnF_AttfP9Oumrx8um4rO-DYY64

belirtec dogrulama:
  dogru oturum, dogru belirtec   gecerli
  baska oturumun belirteci       reddedildi
  imzasi degistirilmis           reddedildi
  belirtec yok                   reddedildi

kaynak denetimi:
  kendi sayfasindan              kabul
  baslik yok, isaret var         kabul
  disaridan gonderim             reddedildi
  ikisi de yok                   reddedildi

Belirteç, sunucu gizlisiyle üretilen bir imza taşır ve imza oturum kimliğini kapsar; başka bir oturumun belirteci sunulduğunda imza uyuşmaz. Karşılaştırma timingSafeEqual ile yapılır: karakter karakter kısa devre yapan bir karşılaştırma, doğru önek uzunluğunu ölçülebilir bir zaman farkına dönüştürür.

Belirtecin taşınma yolu da kuralın parçasıdır. Yalnız bir çerezde taşınırsa saldırı karşısında değeri kalmaz, çünkü çerez de kendiliğinden gider. Belirteç, çerez dışında bir yerde bulunmalıdır: gizli bir form alanı veya uygulamanın açıkça eklediği bir istek başlığı. Uygulama Mimarisi kursundaki Tarayıcıda Kimlik Doğrulama dersi bu ayrımın öteki yüzünü kurmuştu; oturumu bir çerez yerine bellekteki bir belirteçle taşıyan ve isteğe açıkça ekleyen bir tasarımda ortam yetkisi zaten oluşmaz, dolayısıyla bu saldırı sınıfı kendiliğinden ortadan kalkar. Bedeli, oturumun sayfa yenilemesinde yeniden kurulmasıdır.

Üçüncü Katman: Kaynak Denetimi

Çıktının son bölümü üçüncü katmanı gösterir. Sunucu, durum değiştiren her istekte isteğin kaynağını denetler. Origin başlığı bulunuyorsa beklenen kaynakla karşılaştırılır. Bu başlığın bulunmadığı durumlar için tarayıcının eklediği getirme meta bilgisi kullanılır: Sec-Fetch-Site başlığı isteğin aynı kaynaktan mı, aynı siteden mi, yoksa dışarıdan mı başlatıldığını bildirir.

Denetimin son satırı önemlidir: iki başlığın da bulunmadığı istek kabul edilmez. Eksikliği “sorun yok” saymak, denetimi bir başlığın silinmesiyle atlatılabilir hâle getirir. Bu başlıklar uygulama kodunun değil tarayıcının yazdığı başlıklardır ve tarayıcı tarafından değiştirilemez; yokluklarını güvenli yorumlamak için bir neden yoktur.

Yöntem Disiplini

Bütün bu katmanların altında bir varsayım vardır: durum değiştiren işlemler güvenli yöntemlerle yapılmaz. İnternet Nasıl Çalışır kursunda tanımlanan güvenli yöntem kavramı burada bir güvenlik gereğine dönüşür. Ölçüm kaydını silen bir uç GET ile çağrılabiliyorsa, bir görsel ögesi bile o ucu tetikler; Lax çerez davranışı ve pek çok kaynak denetimi bu durumu kapsamaz.

Kural olarak yazma işlemleri POST, PUT, PATCH veya DELETE yöntemleriyle yapılır ve sunucu, yazma uçlarında yöntem denetimini açıkça uygular.

Özet

  • İstek sahteciliği, çerezin isteği kimin başlattığına bakılmadan eklenmesinden doğar; yanıtın okunamaması yan etkinin gerçekleşmesini engellemez.
  • SameSite niteliği ilk katmandır: Strict kaynaklar arası her isteği, Lax üst düzey güvenli gezinme dışındaki her isteği eler; None için Secure zorunludur.
  • Aynı site ölçütü aynı kaynak ölçütünden geniştir; alt alan riski __Host- öneki ve ikinci bir katmanla daraltılır.
  • İstek belirteci oturuma bağlanır, çerez dışında bir yerde taşınır ve sabit zamanlı karşılaştırmayla doğrulanır.
  • Origin ve Sec-Fetch-Site başlıkları üçüncü katmandır; ikisinin de bulunmadığı istek reddedilir ve durum değiştiren işlemler güvenli yöntemlere açılmaz.

Sonraki Adım

Bu dersin savunmaları tek tek başlıklar ve nitelikler üzerinden kuruldu. Aynı yanıt başlığı yüzeyinde, saldırı sınıfına özgü olmayan ama tümünün etkisini azaltan bir küme daha vardır: bağlantının şifresiz kalmasını engelleyen, belgenin başka bir sayfa tarafından çerçevelenmesini sınırlayan ve dışarı sızan adres bilgisini daraltan başlıklar. Sonraki ders bu kümeyi ve eksikliklerini bulan bir yapılandırma denetimini ele alır.

İ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