İçeriğe geç
academia.sh

Ders 16 / 23

Oturum Güvenliği

Girişte oturum kimliğinin yenilenmesi, oturum sabitlemenin gözlenebilir sonucu, eşzamanlı oturumların listelenip tek tek sonlandırılması, boşta kalma ile mutlak ömür sınırlarının birlikte uygulanması ve çerez niteliklerinin rolü.

İçindekiler

Önceki dersin son kuralı, başarılı bir sıfırlamanın hesaba ait tüm oturumları kapatmasıydı. O cümlenin karşılığı, oturumun sunucu tarafında bir kayıt olmasıdır: kapatılabilen şey, kaydı tutulan şeydir. Bu ders oturumun açılışından sonlandırılmasına kadar geçen ömrünü ele alıyor.

Oturum Tabanlı Kimlik Doğrulama dersinde oturumun ne olduğu kurulmuştu: sunucu bir kayıt tutar, kaydın kimliğini tarayıcıya çerezle verir, sonraki isteklerde o kimliği görünce kullanıcıyı tanır. Buradaki sorular kimliğin ne zaman değiştiği, ne kadar yaşadığı ve kimin kapatabildiğidir.

Oturum Sabitleme ve Girişte Yenileme

Kullanıcı siteye girmeden önce de bir oturuma sahip olabilir — dil tercihi, sepet, arama geçmişi gibi durumlar anonim bir oturumda tutulur. Kullanıcı giriş yaptığında bu oturumun ne olacağı bir karardır ve iki seçenek vardır: aynı kimlik girişli sayılır ya da kimlik yenilenir.

Birinci seçenek oturum sabitleme (session fixation) adı verilen soruna açıktır. Kullanıcının giriş öncesindeki oturum kimliğini bilen biri, kullanıcı giriş yaptığı anda aynı kimliğe sahip olmaya devam eder ve artık girişli bir oturumun kimliğini elinde tutar. Kimliğin nasıl öğrenildiği ayrı bir konudur; kurulumun kusuru, öğrenilmiş bir kimliğin giriş sonrasında da geçerli kalmasıdır.

Aşağıdaki blok iki kurulumu yerel bir sunucuda yan yana çalıştırıyor.

cat > oturum-sunucu.mjs <<'EOF'
import { createServer } from "node:http";
import { randomBytes } from "node:crypto";

const oturumlar = new Map();                  // kimlik -> { uye }

const kimlikOku = (istek) =>
  /(?:^|;\s*)oturum=([^;]+)/.exec(istek.headers.cookie ?? "")?.[1];

function yeniOturum(uye = null) {
  const kimlik = randomBytes(24).toString("base64url");
  oturumlar.set(kimlik, { uye });
  return kimlik;
}

const cerez = (kimlik) => `oturum=${kimlik}; Path=/; HttpOnly; SameSite=Lax`;

createServer((istek, yanit) => {
  const yol = new URL(istek.url, "http://yerel").pathname;
  const kimlik = kimlikOku(istek);

  if (yol === "/saglik") return yanit.writeHead(200).end("hazir\n");

  if (yol === "/") {                          // anonim ziyaret: oturum acilir
    if (kimlik && oturumlar.has(kimlik)) return yanit.writeHead(200).end(kimlik + "\n");
    const yeni = yeniOturum();
    return yanit.writeHead(200, { "set-cookie": cerez(yeni) }).end(yeni + "\n");
  }

  if (yol === "/kusurlu/giris") {              // reddedilmesi gereken kurulum
    const o = oturumlar.get(kimlik);
    if (!o) return yanit.writeHead(400).end("oturum yok\n");
    o.uye = "U-1001";                          // ayni kimlik, artik girisli
    return yanit.writeHead(200).end(kimlik + "\n");
  }

  if (yol === "/giris") {                      // dogru kurulum
    if (kimlik) oturumlar.delete(kimlik);      // eski kimlik gecersizlesir
    const yeni = yeniOturum("U-1001");
    return yanit.writeHead(200, { "set-cookie": cerez(yeni) }).end(yeni + "\n");
  }

  if (yol === "/ben") {
    const o = oturumlar.get(kimlik);
    return yanit.writeHead(o ? 200 : 401).end((o ? (o.uye ?? "anonim") : "gecersiz") + "\n");
  }

  yanit.writeHead(404).end("");
}).listen(8495, "127.0.0.1");
EOF
node oturum-sunucu.mjs & sunucu=$!
until curl -sf http://127.0.0.1:8495/saglik > /dev/null; do :; done

dene() {
  kavanoz=$(mktemp); yol=$1
  once=$(curl -s -c "$kavanoz" -b "$kavanoz" http://127.0.0.1:8495/)
  sonra=$(curl -s -c "$kavanoz" -b "$kavanoz" "http://127.0.0.1:8495$yol")
  echo "  giris oncesi kimlik : ${once:0:14}..."
  echo "  giris sonrasi kimlik: ${sonra:0:14}..."
  echo "  kimlik degisti mi   : $([ "$once" = "$sonra" ] && echo hayir || echo evet)"
  echo "  eski kimlikle /ben  : $(curl -s -H "cookie: oturum=$once" http://127.0.0.1:8495/ben)"
  rm -f "$kavanoz"
}

echo "--- reddedilmesi gereken kurulum (/kusurlu/giris) ---"; dene /kusurlu/giris
echo "--- dogru kurulum (/giris) ---";                        dene /giris

kill $sunucu
rm -f oturum-sunucu.mjs
--- reddedilmesi gereken kurulum (/kusurlu/giris) ---
  giris oncesi kimlik : BpWfAGGoOo1Zvs...
  giris sonrasi kimlik: BpWfAGGoOo1Zvs...
  kimlik degisti mi   : hayir
  eski kimlikle /ben  : U-1001
--- dogru kurulum (/giris) ---
  giris oncesi kimlik : qmM42rssGO2qEm...
  giris sonrasi kimlik: w1CarutBqemvD1...
  kimlik degisti mi   : evet
  eski kimlikle /ben  : gecersiz

Kimlik değerleri her çalıştırmada değişir; iki kurulumun farkı değişmez. Reddedilmesi gereken kurulumda giriş öncesinde ele geçmiş bir kimlik, giriş sonrasında U-1001 olarak yanıt alıyor. Doğru kurulumda aynı kimlik gecersiz dönüyor, çünkü giriş anında eski kayıt silinip yenisi açılıyor.

Kuralın yazılışı tek cümledir: yetki düzeyi değiştiren her işlemde oturum kimliği yenilenir. Giriş bunun en belirgin örneğidir; parola değişikliği, ikinci etmenin tamamlanması ve rol yükselmesi de aynı kuralın kapsamındadır. Yenileme sırasında anonim oturumdaki taşınması gereken veriler yeni kayda kopyalanır — dil tercihi gibi. Taşınmayan şey kimliğin kendisidir.

Eşzamanlı Oturumlar

Bir üye aynı anda birden çok yerden girmiş olabilir: şubedeki masaüstünden, telefonundan, salon terminalinden. Bunların her biri ayrı bir oturum kaydıdır ve ayrı ayrı sonlandırılabilir olmalıdır. Kullanıcıya gösterilen “açık oturumlar” listesi, hesabında beklenmedik bir erişim olup olmadığını görebildiği tek yerdir.

Kayıt, oturum kimliğinin kendisini değil karmasını tutar. Gerekçe önceki dersteki sıfırlama belirteciyle aynıdır: veritabanını okuyabilen biri, açık oturumları kullanabilecek durumda olmamalıdır.

cat > oturum-depo.mjs <<'EOF'
import { randomBytes, createHash } from "node:crypto";
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE oturum(
  karma TEXT PRIMARY KEY, uye TEXT NOT NULL, aygit TEXT NOT NULL,
  acilis INTEGER NOT NULL, son_erisim INTEGER NOT NULL)`);

const karmala = (k) => createHash("sha256").update(k).digest("hex");
const BOSTA_MS = 30 * 60 * 1000;              // bosta kalma siniri: 30 dakika
const MUTLAK_MS = 12 * 60 * 60 * 1000;        // mutlak omur: 12 saat
const T0 = Date.parse("2026-03-10T09:00:00Z");   // ornegin "simdi"si

function ac(uye, aygit, acilisFark, erisimFark) {
  const kimlik = randomBytes(24).toString("base64url");
  db.prepare("INSERT INTO oturum VALUES (?,?,?,?,?)")
    .run(karmala(kimlik), uye, aygit, T0 - acilisFark, T0 - erisimFark);
  return kimlik;
}

const dk = 60 * 1000, sa = 60 * dk;
ac("U-1001", "masaustu-sube-1", 2 * sa, 3 * dk);
const telefon = ac("U-1001", "telefon", 1 * sa, 45 * dk);
const eski    = ac("U-1001", "salon-terminali", 14 * sa, 5 * dk);
ac("U-1002", "telefon", 30 * dk, 1 * dk);

const durum = `CASE
  WHEN ? - son_erisim > ${BOSTA_MS} THEN 'bosta doldu'
  WHEN ? - acilis     > ${MUTLAK_MS} THEN 'mutlak omur doldu'
  ELSE 'gecerli' END`;

const listele = (uye) => db.prepare(
  `SELECT aygit, (? - acilis)/60000 AS acilis_dk, (? - son_erisim)/60000 AS bosta_dk,
          ${durum} AS durum
   FROM oturum WHERE uye = ? ORDER BY acilis`).all(T0, T0, T0, T0, uye);

const yaz = (baslik, uye) => {
  console.log(baslik);
  for (const s of listele(uye)) {
    console.log(`  ${s.aygit.padEnd(16)} acilis=${String(s.acilis_dk).padStart(3)} dk  ` +
      `bosta=${String(s.bosta_dk).padStart(2)} dk  ${s.durum}`);
  }
};

yaz("U-1001 oturumlari:", "U-1001");

const silinen = db.prepare("DELETE FROM oturum WHERE karma = ? AND uye = ?")
  .run(karmala(eski), "U-1001").changes;
console.log(`\ntek oturum sonlandirildi (etkilenen satir: ${silinen})`);
yaz("U-1001 oturumlari:", "U-1001");

const hepsi = db.prepare("DELETE FROM oturum WHERE uye = ?").run("U-1001").changes;
console.log(`\nsifirlama sonrasi tum oturumlar kapatildi (etkilenen satir: ${hepsi})`);
console.log("U-1001 kalan oturum :",
  db.prepare("SELECT COUNT(*) s FROM oturum WHERE uye='U-1001'").get().s);
console.log("U-1002 kalan oturum :",
  db.prepare("SELECT COUNT(*) s FROM oturum WHERE uye='U-1002'").get().s);
console.log("telefon kimligi hala gecerli mi:",
  db.prepare("SELECT COUNT(*) s FROM oturum WHERE karma = ?").get(karmala(telefon)).s > 0);
EOF
node oturum-depo.mjs
U-1001 oturumlari:
  salon-terminali  acilis=840 dk  bosta= 5 dk  mutlak omur doldu
  masaustu-sube-1  acilis=120 dk  bosta= 3 dk  gecerli
  telefon          acilis= 60 dk  bosta=45 dk  bosta doldu

tek oturum sonlandirildi (etkilenen satir: 1)
U-1001 oturumlari:
  masaustu-sube-1  acilis=120 dk  bosta= 3 dk  gecerli
  telefon          acilis= 60 dk  bosta=45 dk  bosta doldu

sifirlama sonrasi tum oturumlar kapatildi (etkilenen satir: 2)
U-1001 kalan oturum : 0
U-1002 kalan oturum : 1
telefon kimligi hala gecerli mi: false

Silme deyimindeki AND uye = ? koşulu, sonraki konunun tamamının habercisidir: kullanıcı yalnız kendi oturumunu kapatabilmelidir. Koşul olmadan, kimliği bilen herkes herhangi bir oturumu kapatabilir. Bu, nesne düzeyinde erişim denetiminin bu dersteki ilk görünümüdür.

Son üç satır sıfırlama kuralının kanıtıdır: U-1001’in tüm oturumları kapandı, U-1002’nin oturumu etkilenmedi ve daha önce elde tutulan telefon kimliği artık kayıtta yok.

İki Ömür Sınırı

Çıktının durum sütunu iki ayrı sınırı birlikte değerlendiriyor.

Boşta kalma sınırı, son erişimden bu yana geçen süreyi ölçer. Amacı, kullanıcının başında olmadığı bir aygıtta oturumun açık kalmasını engellemektir. Telefon oturumu bu sınıra takıldı: kırk beş dakikadır kullanılmıyor.

Mutlak ömür, oturumun açılışından bu yana geçen süreyi ölçer ve kullanım ne olursa olsun uygulanır. Amacı, sürekli kullanılan bir oturumun süresiz yaşamasını engellemektir. Salon terminalindeki oturum bu sınıra takıldı: beş dakika önce kullanılmış olmasına karşın on dört saattir açık.

İkisi birlikte bulunur çünkü ayrı sorunları çözer. Yalnız boşta kalma sınırı olan bir kurulumda, ele geçirilmiş bir oturum düzenli isteklerle sonsuza kadar tazelenebilir. Yalnız mutlak ömür olan bir kurulumda, terk edilmiş bir aygıttaki oturum sınıra kadar açık kalır.

Değerlerin seçimi hesabın yetkisine göre değişir. Ödünç masasındaki personel oturumu için kısa boşta kalma sınırı, üye oturumu için daha uzun bir sınır seçilebilir. Ölçüt, oturumun açabildiği kapının genişliğidir.

Sınırların uygulanması bir sorguda yapılır; ayrı bir temizleme görevi ise süresi dolmuş kayıtları siler. İkisi birbirinin yerine geçmez: temizleme görevi gecikirse sorgu yine doğru karar verir, sorgu gecikmeli çalışırsa temizleme kaydı yine de kaldırır.

Kimliğin Taşınması

Oturum kimliği tarayıcıyla sunucu arasında çerezle taşınır ve çerezin nitelikleri kimliğin korunmasının bir parçasıdır. Yukarıdaki sunucu üç nitelik yazıyor.

HttpOnly, çerezin sayfadaki betiklerden okunmasını engeller. İstemci Güvenliği konusunda ele alınan siteler arası betik çalıştırma bir sayfada gerçekleştiğinde, bu nitelik oturum kimliğinin doğrudan okunmasının önüne geçer.

SameSite, çerezin başka sitelerden başlatılan isteklere eklenmesini sınırlar ve siteler arası istek sahteciliğine karşı ilk savunmadır.

Secure niteliği yukarıdaki örnekte yoktur, çünkü yerel sunucu düz HTTP konuşuyor. Gerçek bir dağıtımda bu nitelik zorunludur: çerez yalnız güvenli taşıma üzerinden gönderilir. Taşıma güvenliği olmadan diğer iki niteliğin değeri sınırlıdır — kimlik ağ üzerinde açık gider.

Bu üç nitelik, kimliğin başkasının eline geçmesini — oturumun ele geçirilmesini (session hijacking) — zorlaştırır ama tümüyle engellemez. Bu yüzden sunucu tarafında ikinci bir katman bulunur: oturum kaydında aygıt ve kaynak bilgisi tutulur, beklenmedik bir değişimde oturum sonlandırılıp yeniden kimlik doğrulama istenir. Bu denetimin sıkılığı bir ödünleşimdir; ağ değiştiren bir kullanıcıyı sürekli dışarı atan bir kural, kullanıcıyı oturumu hiç kapatmamaya iter.

Özet

  • Yetki düzeyi değiştiren her işlemde oturum kimliği yenilenir; yenilenmeyen kurulumda giriş öncesi bilinen bir kimlik giriş sonrasında da geçerli kalır.
  • Oturumlar sunucu tarafında kayıt olarak tutulur ve kimliğin karması saklanır; kapatılabilen şey kaydı tutulan şeydir.
  • Bir hesabın eşzamanlı oturumları listelenebilir, tek tek kapatılabilir ve sıfırlama sırasında toplu olarak sonlandırılabilir.
  • Boşta kalma sınırı ile mutlak ömür ayrı sorunları çözer ve birlikte uygulanır.
  • Çerez nitelikleri kimliğin taşınmasını korur; sunucu tarafındaki aygıt denetimi ikinci katmandır ve sıkılığı kullanıcı davranışıyla dengelenir.

Sonraki Adım

Bu konu boyunca soru tekti: sunucu, karşısındakinin kim olduğunu nasıl güvenle biliyor? Oturum silme sorgusundaki AND uye = ? koşulu ise başka bir soruyu işaret etti — kim olduğu bilinen birinin neyi yapabileceği. Sonraki konu bu ikinci soruya geçiyor ve rol tabanlı erişim denetimiyle başlıyor: izinlerin kişiye değil göreve bağlanması, rol devralmasının etkin izin kümesini nasıl gizlediği ve kütüphane personelinin yetkilerinin bir çizelgeden nasıl türetildiği.

İ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