Ders 11 / 15
SQL Enjeksiyonu Önleme
Sorgu metnini istemcinin değerinden ayıran parametreli sorguyu ölçmek: aynı sorgu birleştirmede değer değişince değişir, hazır deyimde değişmez, ve bağlama her yerde çalışmaz.
Yayın Güncelleme
Bu derste
- Birleştirilmiş sorgu metni ile parametreli sorgu metnini karşılaştırır ve hangisinin istemciye bağlı olduğunu ölçer
- Hazır deyim ve bağlama ile değeri sorgu metninden ayırır
- ORDER BY gibi bağlamanın geçerli olmadığı yerde izin listesi kullanır
İçindekiler
Girdi Doğrulama ve Arındırma dersinde doğrulanmış adedi yine istemci belirliyordu; doğrulama yalnız geçerli kümeyi daralttı. Aynı değer bir sorgu metninin içine girerse soru değişir: sorgunun metnini kim belirliyor? Metin istemcinin değerinden oluşuyorsa, sorgunun ne yapacağı da ondan oluşur.
GÜ2. Bu derste sorguya giren değer nötr bir addır: ayse ve mehmet. İki ad da geçerli kullanıcı adıdır; ölçülen şey, adın değişmesiyle sorgu metninin değişip değişmediğidir. Sorgu metnini bozacak girdiler yazılmaz ve denenmez. Bütün sorgular bellekteki veritabanında koşar.
Ortak Veri
Ölçümler aynı küçük tabloyu kullanır: üç kullanıcı, kasıtlı olarak alfabe dışı sırayla eklenmiş. Dosya yalnız diske yazılır.
<?php // veri.php — bellekteki veritabanı ve üç kullanıcı declare(strict_types=1); function baglan(): PDO { $pdo = new PDO('sqlite::memory:'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->exec('CREATE TABLE kullanicilar (id INTEGER PRIMARY KEY, ad TEXT)'); foreach (['mehmet', 'zeynep', 'ayse'] as $ad) { $pdo->prepare('INSERT INTO kullanicilar (ad) VALUES (?)')->execute([$ad]); } return $pdo; }
Birleştirme ve Parametreli Sorgu
Birleştirilmiş sorguda değer, metnin parçası olur. Parametreli sorguda (prepared statement) metin sabittir ve değer ayrı bir adımda bağlanır (binding): prepare() önce yalnızca yer tutuculu metni hazırlar, execute() değeri o metne yerleştirmeden, ayrı iletir. Hazır deyimin elindeki metne queryString özelliğinden bakılabilir; aşağıdaki ölçüm iki yöntemin sorgu metnini iki adla karşılaştırır.
<?php // sorgu.php — iki yöntemin sorgu metni declare(strict_types=1); require 'veri.php'; function birlestirmeMetni(string $ad): string { return "SELECT id, ad FROM kullanicilar WHERE ad = '" . $ad . "'"; } function parametreliMetin(PDO $pdo, string $ad): string { $deyim = $pdo->prepare('SELECT id, ad FROM kullanicilar WHERE ad = ?'); $deyim->execute([$ad]); return $deyim->queryString; }
Dosya yalnız fonksiyonları tanımlar. Ölçüm, iki adla iki yöntemi yan yana çağırır.
<?php // karsilastir.php — iki yöntemde sorgu metni declare(strict_types=1); require 'sorgu.php'; $pdo = baglan(); foreach (['ayse', 'mehmet'] as $ad) { echo 'birleştirme ', birlestirmeMetni($ad), "\n"; echo 'parametreli ', parametreliMetin($pdo, $ad), "\n"; }
birleştirme SELECT id, ad FROM kullanicilar WHERE ad = 'ayse' parametreli SELECT id, ad FROM kullanicilar WHERE ad = ? birleştirme SELECT id, ad FROM kullanicilar WHERE ad = 'mehmet' parametreli SELECT id, ad FROM kullanicilar WHERE ad = ?
Birleştirmede ad değişince sorgu metninin kendisi değişti: 'ayse' yerine 'mehmet' metne yazıldı. Parametreli sorguda metin iki adda da aynı kaldı; ad metnin dışında, bağlanan değer olarak taşındı. Bu fark güvenlik özelliğinin tamamıdır: metnin değeri istemciden alınmış olması, metnin yapısını istemcinin değiştirebileceği anlamına gelir. Metin sabit olduğunda değer ne olursa olsun yalnız değer olarak kalır.
Taraf: Metni Kim Belirliyor
Aynı ölçümü kursun sorusuna bağlayalım. Çekirdek dosya, her derste olduğu gibi taraf.php’dir; sina() sonucu sapmasız hesaplar, sonra her dış tarafın sapmasıyla yeniden hesaplar ve taraf() değiştiren ilk tarafı seçer. Karar sırası istemci, üçüncü taraf, ortam; hiçbiri değiştiremediyse sunucu.
<?php // taraf.php — bu sonucu kim değiştirebiliyor? declare(strict_types=1); // Kodun dışındaki taraflar, karar sırasıyla: her istek, her dış çağrı, her dağıtım. const DIS_TARAFLAR = ['istemci', 'üçüncü taraf', 'ortam']; /** Sonucu bir kez sapmasız, sonra her dış tarafın sapmasıyla ayrı ayrı hesaplar. */ function sina(callable $sonuc, array $sapmalar): array { $taban = $sonuc([]); $degisti = []; foreach (DIS_TARAFLAR as $taraf) { if (array_key_exists($taraf, $sapmalar)) { $degisti[$taraf] = $sonuc([$taraf => $sapmalar[$taraf]]) !== $taban; } } return $degisti; } /** Sonucu değiştirebilen ilk dış taraf; hiçbiri değiştiremiyorsa sunucu. */ function taraf(array $degisti): string { foreach (DIS_TARAFLAR as $t) { if ($degisti[$t] ?? false) return $t; } return 'sunucu'; } function tarafRaporu(string $ad, callable $sonuc, array $sapmalar): string { $degisti = sina($sonuc, $sapmalar); $satirlar = []; foreach ($degisti as $t => $d) $satirlar[] = $t . ($d ? ' değiştirdi' : ' değiştiremedi'); $sonTaraf = taraf($degisti); echo "$ad → $sonTaraf\n ", implode(' · ', $satirlar), "\n"; return $sonTaraf; }
Sonuç, prepare()’e giden sorgu metnidir. İstemcinin sapması ayse yerine mehmet göndermektir.
<?php // enjeksiyon.php — sorgu metninin tarafı declare(strict_types=1); require 'taraf.php'; require 'sorgu.php'; $pdo = baglan(); tarafRaporu('birleştirme', fn(array $s) => birlestirmeMetni($s['istemci'] ?? 'ayse'), ['istemci' => 'mehmet']); tarafRaporu('parametreli', fn(array $s) => parametreliMetin($pdo, $s['istemci'] ?? 'ayse'), ['istemci' => 'mehmet']);
birleştirme → istemci
istemci değiştirdi
parametreli → sunucu
istemci değiştiremedi
Birleştirmede sonucu istemci değiştirdi. Parametreli sorguda metni değiştirebilecek üç taraf da değiştiremedi; kalan sahip sunucudur, çünkü metin kodda sabit yazılmıştır. Burada kaçışlama ya da doğrulama gibi bir daralma yoktur: sorgu metninin sahibi tümüyle el değiştirmiştir. Doğrulama değerin kümesini daraltır ve istemci kalır; hazır deyim ise metni istemcinin erişemeyeceği bir yere koyar.
Sınırlayıcı Ölçüm
Bağlama her yerde çalışmaz: yer tutucu yalnız değerin yerine konur; sütun adı, tablo adı ya da sıralama yönü gibi sorgu yapısının parçaları bağlanamaz. ORDER BY ? yazıp sütun adını bağlayan kişi, sıralamayı bağlamış olmaz; sayı ya da dizge sabiti bağlamış olur. Aşağıda aynı kullanicilar tablosu ad sütununa göre sıralanmak istenir; bağlama ile sıralama istenen sütunun adı verilir.
<?php // siralama.php — bağlama ve izin listesi declare(strict_types=1); require 'veri.php'; const SIRALAMA_IZINLERI = ['ad' => 'ad', 'id' => 'id']; function baglamaIleSirala(PDO $pdo, string $sutun): array { $deyim = $pdo->prepare('SELECT ad FROM kullanicilar ORDER BY ?'); $deyim->execute([$sutun]); return $deyim->fetchAll(PDO::FETCH_COLUMN); } function izinListesiyleSirala(PDO $pdo, string $istek): array { $sutun = SIRALAMA_IZINLERI[$istek] ?? 'id'; return $pdo->query('SELECT ad FROM kullanicilar ORDER BY ' . $sutun)->fetchAll(PDO::FETCH_COLUMN); } $pdo = baglan(); echo 'bağlama ', implode(', ', baglamaIleSirala($pdo, 'ad')), "\n"; echo 'izin listesi ', implode(', ', izinListesiyleSirala($pdo, 'ad')), "\n"; echo 'listede yok ', implode(', ', izinListesiyleSirala($pdo, 'sifre')), "\n";
bağlama mehmet, zeynep, ayse izin listesi ayse, mehmet, zeynep listede yok mehmet, zeynep, ayse
Bağlamayla sıralama beklenen sonucu vermedi: satırlar ad sırasıyla değil, eklenme sırasıyla geldi (mehmet, zeynep, ayse). Bağlanan 'ad' bir sütun adı olarak değil, her satırda aynı olan bir dizge değeri olarak okundu ve sıralama bir şey değiştirmedi. Sorgu hata da vermedi; yanlışlık sessizdir. İzin listesi sıralamayı gerçekten uyguladı (ayse, mehmet, zeynep) ve listede olmayan sifre isteği hataya düşmeden varsayılan id sırasına döndü. Sütun adı sorguya girerse de girdiği yer kodda sabit yazılı bir listedir; istemcinin değeri yalnız listeden bir anahtar seçer.
Özet
- Bu dersin ölçü sınıfı birleştirmede istemci, parametreli sorguda sunucu: sorgu metnini ikincisinde yalnızca
prepare()’e verilen sabit dizge belirler. - Birleştirmede adı
ayse’denmehmet’e değiştirmek sorgu metnini değiştirdi; parametreli sorguda metin iki adda da aynı kaldı. - Hazır deyim ve bağlama değeri metinden ayırır; savunma sonucu sahibinden alır, daraltmakla yetinmez.
- Bağlama yalnız değer içindir:
ORDER BY ?sıralamayı değiştirmedi ve sütun adı bağlanamadı. - Sorgu yapısının parçası istemciden geliyorsa çözüm, kod içinde sabit bir izin listesinden seçmektir; listede olmayan istek varsayılana düşer.
Sonraki Adım
Sorgu metnini sunucuya almak bir yöndeki yolu kapattı. Aynı değer bu kez bir sayfaya basılırsa, çıktının metnini kim belirler? Siteler Arası Betik Çalıştırma Önleme dersi, çıktı anında kaçışlamayı aynı taraf sorusuyla ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.