İçeriğe geç
academia.sh

Ders 08 / 16

Kötüye Kullanım Savunması

Kötüye kullanım savunmasının iki yapılandırma kararı: sınırın hangi anahtara uygulandığı (istemci, hesap, kaynak) ve yanıtın bir kaydın varlığını ele verip vermediği; kilitlemenin durdurduğu deneme ile dışarıda bıraktığı doğru üye aynı koşumda sayılır.

İçindekiler

Denetim kaydı sonradan sorar ve iyi yanıt verir, ama hiçbir şeyi durdurmaz. Ödünç sisteminin dışarıya açık uçlarına gelen isteklerin bir bölümü ne bir açığı zorlar ne bir kural ihlal eder; yalnızca çok fazladır — bir hesabın parolasını arayan denemeler, üye kimliklerini sırayla yoklayan istekler, katalogun tamamını çeken bir istemci. Bunları durduran düzenek hız sınırlamadır ve algoritması ile yerleşimi bu müfredatın önceki kurslarında ölçüldü; burada ikisi de girdi olarak alınır. Bu dersin ölçtüğü iki karar başkadır: sınırın kime uygulandığı ve uygulamanın ne yanıt verdiği.

Sınır Kime Uygulanıyor

BG18 — trafik dört bileşenden oluşur: kırk üyenin olağan isteği (on ikisi kütüphane şubesinin ortak çıkış adresini paylaşır), tek bir hesabı hedefleyen iki yüz dağıtık deneme, tek bir adresten gelen üç yüz kimlik yoklaması ve kimliği doğrulanmış tek bir hesaptan gelen iki bin kayıt çekme. Sınır değeri ve algoritma beş düzende de aynıdır; değişen tek şey sayacın hangi alana bağlandığıdır.

// anahtar.mjs — sinir kime uygulaniyor: istemci, hesap, hedef
const SINIR = 20;          // pencere basina istek; algoritma ve yerlesim girdi olarak alinir

const istek = [];
const ekle = (n, y) => { for (let i = 0; i < n; i++) istek.push(y(i)); };
// Iyi trafik: 40 uye, her biri 1 giris (kimliksiz) + 5 kimlikli istek; 12'si sube adresini paylasiyor.
for (let u = 1; u <= 40; u++) {
  const adres = u <= 12 ? 'A-sube' : `A-uye-${u}`;
  ekle(1, () => ({ adres, hesap: null, hedef: `U-${u}`, kotu: false }));
  ekle(5, () => ({ adres, hesap: `U-${u}`, hedef: `U-${u}`, kotu: false }));
}
// Dagitik kaba kuvvet: 200 ayri adres, tek hesap.
ekle(200, (i) => ({ adres: `A-dagitik-${i}`, hesap: null, hedef: 'U-17', kotu: true }));
// Numaralandirma: tek adres, 300 ayri hedef.
ekle(300, (i) => ({ adres: 'A-tarama', hesap: null, hedef: `U-${1000 + i}`, kotu: true }));
// Kazima: kimlikli tek hesap, 2000 ayri kaynak.
ekle(2000, (i) => ({ adres: 'A-kazima', hesap: 'U-39', hedef: `K-${i}`, kotu: true }));

// Bir duzen, istegi bir veya birkac anahtara ve her anahtar icin bir sinira baglar.
const duzenler = {
  'istemci': (r) => [['a', r.adres, SINIR]],
  'hesap': (r) => (r.hesap ? [['h', r.hesap, SINIR]] : []),
  'hedef': (r) => [['t', r.hedef, SINIR]],
  'ucu birden': (r) => [['a', r.adres, SINIR], ['t', r.hedef, SINIR], ...(r.hesap ? [['h', r.hesap, SINIR]] : [])],
  'katmanli': (r) => (r.hesap ? [['h', r.hesap, SINIR]] : [['a', r.adres, SINIR], ['t', r.hedef, SINIR]]),
};

function olc(duzen, normalize = (d) => d, akis = istek) {
  const sayac = new Map();
  let durdurulanKotu = 0, gecenKotu = 0, yanlisKalma = 0;
  for (const r of akis) {
    let dusur = false;
    for (const [tur, ham, sinir] of duzen(r)) {
      const anahtar = `${tur}:${normalize(ham)}`;
      const n = (sayac.get(anahtar) ?? 0) + 1;
      sayac.set(anahtar, n);
      if (n > sinir) dusur = true;
    }
    if (r.kotu) dusur ? durdurulanKotu++ : gecenKotu++;
    else if (dusur) yanlisKalma++;
  }
  return { durdurulanKotu, gecenKotu, yanlisKalma };
}

const s = (x, n) => String(x).padStart(n);
const kotu = istek.filter((r) => r.kotu).length;
console.log(`iyi istek ${istek.length - kotu}, kotu istek ${kotu}, sinir ${SINIR}`);
console.log(`${'anahtar'.padEnd(13)}${s('durdurulan', 12)}${s('gecen', 7)}${s('yanlis kalma', 14)}`);
for (const [ad, duzen] of Object.entries(duzenler)) {
  const r = olc(duzen);
  console.log(`${ad.padEnd(13)}${s(r.durdurulanKotu, 12)}${s(r.gecenKotu, 7)}${s(r.yanlisKalma, 14)}`);
}

// Ayni 200 deneme, hedef adi on ayri yazimla verilirse.
const yazimlar = ['U-17', 'u-17', ' U-17', 'U-017', 'U-17 ', 'u-017', 'U_17', 'u_17', 'U-17\t', 'U-0017'];
const yayilmis = istek.map((r) => (r.kotu && r.hedef === 'U-17'
  ? { ...r, hedef: yazimlar[istek.indexOf(r) % yazimlar.length] } : r));
const duzelt = (d) => d.trim().replace(/_/g, '-').toUpperCase().replace(/^U-0+/, 'U-');
console.log('\nayni 200 deneme, hedef adi on ayri yazimla verilirse (katmanli duzen):');
for (const [ad, n] of [['ham anahtar', (d) => d], ['normalize edilmis', duzelt]]) {
  const r = olc(duzenler.katmanli, n, yayilmis);
  console.log(`  ${ad.padEnd(18)} durdurulan ${r.durdurulanKotu}, gecen ${r.gecenKotu}, yanlis kalma ${r.yanlisKalma}`);
}
iyi istek 240, kotu istek 2500, sinir 20
anahtar        durdurulan  gecen  yanlis kalma
istemci              2260    240            52
hesap                1985    515             0
hedef                 186   2314             0
ucu birden           2451     49            52
katmanli             2446     54             0

ayni 200 deneme, hedef adi on ayri yazimla verilirse (katmanli duzen):
  ham anahtar        durdurulan 2266, gecen 234, yanlis kalma 0
  normalize edilmis  durdurulan 2446, gecen 54, yanlis kalma 0

İlk üç satır her anahtarın tam olarak bir kötüye kullanım biçimini gördüğünü söylüyor. İstemci anahtarı yoklamayı ve kazımayı durdurur (2.260), dağıtık denemeyi hiç görmez ve şubenin ortak adresinden gelen elli iki doğru isteği düşürür. Hesap anahtarının yanlış kalması sıfırdır ama kimliksiz gelen beş yüz isteğe hiç uygulanmaz. Hedef anahtarı dağıtık denemeyi yakalayan tek anahtardır ve yalnız yüz seksen altı istek durdurur.

Son iki satır kararın kendisidir. Üç anahtarı birden uygulamak 2.451 istek durdurur ve şubenin elli iki isteğini yine düşürür. Katmanlı düzen beş istek daha az durdurur (2.446) ve yanlış kalmayı sıfıra indirir: kimliği doğrulanmış istek en dar anahtara, yani hesaba bağlanır; kimliksiz istek hem adrese hem hedefe bağlanır. Aynı sınır değeri, aynı algoritma, aynı yerleşim — fark yalnızca sayacın hangi alana bakacağı kararındadır.

Son bloktaki iki satır bu kararın sessiz yanlışıdır. Anahtar, istekle gelen bir tanımlayıcıdan türetiliyorsa ve normalize edilmiyorsa, aynı hesabı on ayrı yazımla yoklayan iki yüz deneme on ayrı sayaca dağılır: durdurulan 2.446’dan 2.266’ya iner, yani yüz seksen deneme sınırın altından geçer. Hiçbir yerde hata yoktur; sayaç doğru sayar, sınır doğru uygulanır, yalnızca on ayrı şeyi sayar.

Yanıtın Kendisi

Sınır, çok fazla isteği durdurur. Numaralandırmanın sorunu ise az sayıda isteğin de yeterli olmasıdır: kırk yoklama, kırk kimliğin hangisinin var olduğunu söylüyorsa sınır hiç devreye girmeden bilgi dışarı çıkmış olur. Bunu belirleyen şey yanıtın biçimidir. Aşağıdaki koşum aynı ucun üç sürümünü gerçekten ayağa kaldırır ve kırk kimliği yoklar; kimlikler dönüşümlü sırayla gider.

// yanit.mjs — yanit bicimi numaralandirmayi mumkun kiliyor mu: imza farki ve sure farki
import { createServer } from 'node:http';
import { createHash } from 'node:crypto';

const TUR = 60000;                 // dogrulayicinin yaptigi sabit is: ozet turu
const ESIK = 5;                    // ms; iki kumeyi ayirmak icin sabit esik (bkz. metin)
const isYap = () => { let d = 'x'; for (let i = 0; i < TUR; i++) d = createHash('sha256').update(d).digest('hex'); };
const uyeler = new Set(Array.from({ length: 20 }, (_, i) => `U-${i + 1}`));
const ALINDI = '{"durum":"istek alindi"}';

const surumler = {
  'ayri yanit': (v) => { if (v) isYap(); return v ? [202, '{"durum":"kod gonderildi"}'] : [404, '{"hata":"kayit yok"}']; },
  'ayni yanit': (v) => { if (v) isYap(); return [202, ALINDI]; },
  'ayni yanit + sabit is': () => { isYap(); return [202, ALINDI]; },
};

const sunucu = createServer((istek, yanit) => {
  const [kod, govde] = surumler[istek.headers['x-surum']](uyeler.has(istek.headers['x-kimlik']));
  yanit.writeHead(kod, { 'content-type': 'application/json' });
  yanit.end(govde);
});
sunucu.listen(8952, '127.0.0.1');
const TABAN = 'http://127.0.0.1:8952/parola-sifirlama';

// Deneme sirasi donusumlu: var olan ve olmayan kimlikler sirayla, boylece surukleme iki kumeyi
// birden etkiler ve sure farki yalnizca dalin isinden gelir.
const kimlikler = Array.from({ length: 20 }, (_, i) => [`U-${i + 1}`, `U-${i + 21}`]).flat();
const dene = async (surum, kimlik) => {
  const t = performance.now();
  const y = await fetch(TABAN, { method: 'POST', headers: { 'x-surum': surum, 'x-kimlik': kimlik } });
  const govde = await y.text();
  return { sure: performance.now() - t, imza: `${y.status}|${govde}`, gercek: uyeler.has(kimlik) };
};
for (let i = 0; i < 5; i++) await dene('ayni yanit + sabit is', 'U-1');   // isinma

const s = (x, n) => String(x).padStart(n);
console.log(`${'surum'.padEnd(23)}${s('imza var/yok', 14)}${s('imza ayrik', 12)}`
  + `${s('sureyle ayirt', 15)}${s('ozet turu var/yok', 19)}`);
for (const surum of Object.keys(surumler)) {
  const olcum = [];
  for (const k of kimlikler) olcum.push(await dene(surum, k));
  const imza = (kume) => new Set(olcum.filter((o) => o.gercek === kume).map((o) => o.imza));
  const [iVar, iYok] = [imza(true), imza(false)];
  const ayrik = [...iVar].every((x) => !iYok.has(x));
  const dogru = olcum.filter((o) => (o.sure > ESIK) === o.gercek).length;
  const is = surum === 'ayni yanit + sabit is' ? `${TUR}/${TUR}` : `${TUR}/0`;
  console.log(`${surum.padEnd(23)}${s(`${iVar.size}/${iYok.size}`, 14)}${s(String(ayrik), 12)}`
    + `${s(`${dogru}/40`, 15)}${s(is, 19)}`);
}
sunucu.close();
console.log(`\nsure esigi ${ESIK} ms sabittir; 20/40 rastgele tahmin duzeyidir`);
console.log(`ozet turu sayisi kosumdan bagimsizdir, sureler degildir`);
surum                    imza var/yok  imza ayrik  sureyle ayirt  ozet turu var/yok
ayri yanit                        1/1        true          40/40            60000/0
ayni yanit                        1/1       false          40/40            60000/0
ayni yanit + sabit is             1/1       false          20/40        60000/60000

sure esigi 5 ms sabittir; 20/40 rastgele tahmin duzeyidir
ozet turu sayisi kosumdan bagimsizdir, sureler degildir

İlk sürüm yanıtı ikiye ayırır: var olan kimliğe 202 ve “kod gönderildi”, olmayana 404 ve “kayıt yok”. İmza kümeleri ayrıktır, dolayısıyla kırk yoklamanın kırkı doğru sınıflanır. Bu, bir açık değil bir yanıt biçimi kararıdır ve genellikle kullanıcıya yardım etme amacıyla verilir.

İkinci sürüm iki dala da aynı gövdeyi ve aynı durum kodunu döndürür; imza ayrımı kapanır. Süre ayrımı ise açık kalır ve kırk yoklamanın kırkı yine doğru sınıflanır, çünkü yalnız var olan kimlikte doğrulayıcı çalışır. Koşumdan bağımsız nicelik son sütundadır: bir dalda altmış bin özet turu, ötekinde sıfır. Bu koşumda ortancalar yaklaşık 18,5 milisaniyeye karşılık 1,4 milisaniyedir; oranın kendisi makineye bağlıdır, varlığı bağlı değildir.

Üçüncü sürüm işi iki dalda da yapar ve süreyle ayırt etme rastgele tahmin düzeyine iner (20/40). Yanıtı eşitlemek tek başına yetmez; yapılan iş de eşitlenmelidir. Bu, ilk iki sürümün hiçbir hata üretmediği anlamına gelir — üçü de çalışır, üçü de doğru yanıt verir ve aralarındaki fark yalnızca yoklayarak ölçülür.

Kilitlemenin Yanlış Kalması

Kaba kuvvete karşı ilk akla gelen önlem, üst üste birkaç hatadan sonra hesabı kilitlemektir. BG19 — bir giriş oturumundaki hatalı deneme sayısı yukarıdaki dağılımdan gelir ve kırk üyenin otuz günü üretilir. BG20 — saldırgan üç saniyede bir deneme yapar, ölçüm penceresi dokuz yüz saniyedir.

// kilit.mjs — kaba kuvvette kilitlemenin yanlis kalma maliyeti
const TOHUM = 9137;
let durum = TOHUM;
const rast = () => (durum = (durum * 1103515245 + 12345) % 2147483648) / 2147483648;

const PENCERE = 900;        // sn: olcum penceresi
const HIZ = 3;              // sn: saldirganin iki denemesi arasi
const ESIK = 5;             // ust uste hatali deneme
const KILIT = 900;          // sn: kilit suresi
const UYE = 40, GUN = 30;

// BG19: bir giris oturumunda basarisiz deneme dagilimi (dogru uyenin kendi hatasi)
const DAGILIM = [[0, 0.72], [1, 0.15], [2, 0.06], [3, 0.03], [4, 0.02], [6, 0.015], [8, 0.005]];
const oturumlar = [];
for (let i = 0; i < UYE * GUN; i++) {
  let x = rast(), hata = 0;
  for (const [n, p] of DAGILIM) { if ((x -= p) < 0) { hata = n; break; } }
  oturumlar.push(hata);
}
const yanlisKalma = oturumlar.filter((h) => h >= ESIK).length;

// Artan beklemede n. denemeden once 2^(n-1) sn beklenir; pencereye kac deneme sigar.
let artan = 0;
for (let t = 0, n = 1; t + 2 ** (n - 1) <= PENCERE; n++) { t += 2 ** (n - 1); artan = n; }

const politikalar = [
  ['sinir yok', PENCERE / HIZ, 0, 0, 0, 0],
  ['kilit, 15 dk', ESIK, yanlisKalma, 0, UYE, KILIT],
  ['kilit, kalici', ESIK, yanlisKalma, yanlisKalma, UYE, -1],
  ['artan bekleme', artan, 0, 0, 0, 2 ** (artan - 1)],
];

const s = (x, n) => String(x).padStart(n);
console.log(`tohum ${TOHUM}: ${oturumlar.length} giris oturumu (${UYE} uye x ${GUN} gun), pencere ${PENCERE} sn`);
console.log(`${'politika'.padEnd(15)}${s('gecen deneme', 14)}${s('kilitlenen dogru', 18)}`
  + `${s('gorevli acma', 14)}${s('hedefli kilit', 15)}${s('dogru uye beklemesi', 21)}`);
for (const [ad, gecen, kilitli, acma, hedefli, bekleme] of politikalar) {
  console.log(`${ad.padEnd(15)}${s(gecen, 14)}${s(kilitli, 18)}${s(acma, 14)}${s(hedefli, 15)}`
    + `${s(bekleme === -1 ? 'gorevli acar' : `${bekleme} sn`, 21)}`);
}
console.log(`\nhedefli kilitleme ${UYE * ESIK} istekle ${UYE} uyeyi disarida birakir`);
console.log(`ust uste ${ESIK} ve uzeri hata iceren oturum ${yanlisKalma}/${oturumlar.length}`
  + ` (%${(100 * yanlisKalma / oturumlar.length).toFixed(1)}); tamami dogru uyedir`);
tohum 9137: 1200 giris oturumu (40 uye x 30 gun), pencere 900 sn
politika         gecen deneme  kilitlenen dogru  gorevli acma  hedefli kilit  dogru uye beklemesi
sinir yok                 300                 0             0              0                 0 sn
kilit, 15 dk                5                30             0             40               900 sn
kilit, kalici               5                30            30             40         gorevli acar
artan bekleme               9                 0             0              0               256 sn

hedefli kilitleme 200 istekle 40 uyeyi disarida birakir
ust uste 5 ve uzeri hata iceren oturum 30/1200 (%2.5); tamami dogru uyedir

Kilitleme işini yapar: dokuz yüz saniyede geçen deneme sayısı üç yüzden beşe iner. Bedeli iki yerde birden görünür. Birincisi ayda otuz doğru üye oturumudur — bin iki yüz oturumun yüzde 2,5’i — ve bunların hiçbiri saldırgan değildir; kalıcı kilitte otuzu da bir görevli işine dönüşür. İkincisi daha ağırdır: kilit, hesap adını bilen birine bir kapatma düğmesi verir. Kırk hesabın her birine beşer deneme, yani iki yüz istek, kırk üyenin tamamını dışarıda bırakır. Kırk üyeyi dışarıda bırakmak için hiçbir parolayı bilmek gerekmez.

Artan bekleme aynı pencerede dokuz denemeye izin verir — kilitten dört fazla, sınırsız düzenden otuz üç kat az — ve hiçbir doğru üyeyi kilitlemez. Kalan bedel son sütundadır: hedeflenen hesabın gerçek sahibi, saldırganın yaktığı denemelerden sonra 256 saniye bekler. Yani gecikme de bir yanlış kalma üretir, ama süresi sonludur ve görevli işi gerektirmez. İki savunma arasındaki seçim “hangisi daha güvenli” sorusu değil, yanlış kalmanın kimin üzerinde ve ne kadar kalacağı sorusudur.

Özet

  • Sınırın algoritması ve yerleşimi bu dersin girdisidir; ölçülen karar sınırın hangi anahtara uygulandığıdır ve her anahtar kötüye kullanımın yalnız bir biçimini görür.
  • İstemci anahtarı 2.260 kötü istek durdurup ortak adres yüzünden 52 doğru isteği düşürdü; katmanlı düzen 2.446 durdurup yanlış kalmayı sıfıra indirdi — kimlikli istek hesaba, kimliksiz istek adres ile hedefe bağlanarak.
  • Anahtar normalize edilmediğinde aynı hesabı on yazımla yoklayan iki yüz deneme on sayaca dağıldı ve durdurulan istek 2.446’dan 2.266’ya indi; sayaç doğru çalışıyor, yalnız on ayrı şeyi sayıyor.
  • Var olan ve olmayan kimliğe ayrı yanıt vermek kırk yoklamanın kırkını ele verdi; yanıtı eşitlemek imza ayrımını kapattı ama süre ayrımı kırkta kırk kaldı, çünkü iş yalnız bir dalda yapılıyordu.
  • İşi iki dalda da yapmak ayırt etmeyi rastgele tahmin düzeyine (20/40) indirdi; koşumdan bağımsız ölçü, iki dalda yapılan özet turu sayısının eşitlenmesidir.
  • Kilitleme geçen denemeyi 300’den 5’e indirdi ve ayda 30 doğru üye oturumunu kilitledi; iki yüz istekle kırk üyenin tamamı dışarıda bırakılabiliyor. Artan bekleme dokuz denemeye izin verdi, kimseyi kilitlemedi ve doğru üyeye 256 saniyelik sonlu bir bekleme bıraktı.

Sonraki Adım

Bu konu boyunca kurulan bütün savunmalar uygulamanın içindedir. Doğrulayıcı, kodlayıcı, başlık katmanı, gizli değer deposu, denetim kaydı ve sayaç — hepsi ödünç servisinin kendi sürecinde çalışır ve hepsi, uygulamanın gördüğü isteğin gerçekten gelen istek olduğunu varsayar. Üretimde bu varsayım kendiliğinden doğru değildir. Uygulama orada yalnız başına durmaz: önünde bağlantıyı karşılayan bir vekil, altında süreci ayakta tutan bir yönetici vardır ve ikisinin yapılandırması uygulamanın gördüğü isteği değiştirir — istemci adresini, gövde boyunu, başlıkları, hatta sürecin kaç kopya çalıştığını. Bu dersteki sayaçların hangi adresi saydığı da oraya bağlıdır. Sonraki ders uygulamanın önündeki katmanı ele alır ve aynı kuralı orada ölçer: yanlış yapılandırma hata vermez, ölçü farkı gösterir.

İ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