---
title: 'Kötüye Kullanım Savunması'
source: 'https://academia.sh/tr/kurslar/backend-uretim/kotuye-kullanim-savunmasi'
course: 'Sunucu Güvenliği ve Üretime Alma'
language: tr
updated: '2026-08-17T18:06:49+00:00'
license: 'CC BY-SA 4.0'
---

# 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.

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.

```js
// 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.

```js
// 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.

```js
// 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.
