SQL enjeksiyonu nedir, nasıl önlenir?
SQL enjeksiyonu, kullanıcıdan gelen bir değerin veritabanı sorgusunun içine metin birleştirerek eklenmesi sonucu ortaya çıkar. Uygulama, kullanıcının yazdığını veri olarak değil sorgunun bir parçası olarak veritabanına gönderir; böylece girdi, sorgunun anlamını değiştirebilir. Sonuç; yetkisiz veri okuma, kayıtların değiştirilmesi ya da silinmesi, giriş kontrolünün atlanması olabilir.
İyi haber şudur: SQL enjeksiyonu, nedeni ve çözümü en iyi bilinen açıklardan biridir. Sorguları hazır ifadelerle (prepared statement) yazmak sorunu kaynağında çözer. Bu rehber, kavramı ve PHP ile PDO üzerinden doğru uygulamayı anlatır.
Sorun nerede başlar, nerede biter?
-
Veri ile komut birbirine karışır
Bir arama kutusu, giriş formu, adres çubuğundaki bir
idparametresi ya da bir çerez değeri; hepsi kullanıcının denetimindedir. Uygulama bu değeri tırnak içine alıp sorgu metnine eklediğinde, veritabanı hangi kısmın geliştiricinin yazdığı komut, hangi kısmın kullanıcının verisi olduğunu ayırt edemez. Açığın özü budur: veri ile komutun aynı metinde birleşmesi.
-
Hazır ifade veriyi komuttan ayırır
Hazır ifadede sorgunun yapısı ve veriler veritabanına ayrı ayrı gönderilir. Sorgu metninde değerlerin yerine yer tutucular (
?ya da:ad) bulunur; değerler sonradan bağlanır ve hiçbir koşulda komut olarak yorumlanmaz. Kullanıcı ne yazarsa yazsın, yazdığı yalnızca aranacak ya da kaydedilecek bir veridir.<?php // YANLIŞ: kullanıcı girdisi sorgu metnine birleştiriliyor // $sql = "SELECT id, ad FROM urunler WHERE kategori = '" . $_GET['kategori'] . "'"; // DOĞRU: yer tutucu + bağlama $sorgu = $pdo->prepare('SELECT id, ad FROM urunler WHERE kategori = ? AND durum = ?'); $sorgu->execute([$_GET['kategori'] ?? '', 1]); $urunler = $sorgu->fetchAll();
-
Bağlanamayan yerlerde izin listesi kullanılır
Yer tutucular yalnızca değerler için kullanılabilir. Tablo adı, kolon adı, sıralama yönü (
ASC/DESC) gibi sorgunun yapısal parçaları bağlanamaz. Bu parçalar kullanıcının seçimine bağlıysa, girdi doğrudan sorguya konmaz; önceden tanımlı bir izin listesinden eşleştirilir.
-
Zararı sınırlayan ikinci katmanlar
Hazır ifade asıl çözümdür; yine de bir yerde hata yapılmış olabilir. Uygulamanın veritabanı kullanıcısına yalnızca ihtiyaç duyduğu yetkileri vermek, hata ayrıntılarını ziyaretçiye göstermemek ve girdileri türüne göre doğrulamak, olası bir açığın etkisini küçültür.
Belirtiler: nasıl fark edilir?
- Kod incelemesinde: Sorgu metninin içinde değişken birleştirme (
"... WHERE id = " . $idya da çift tırnak içinde$degisken) görüyorsanız orası şüphelidir.$_GET,$_POST,$_COOKIEve$_SERVERdeğerlerinin sorguya ulaştığı her yolu izleyin. - Sitede: Bir alana tek tırnak gibi özel bir karakter yazıldığında sayfanın veritabanı hatası göstermesi, o girdinin sorguya kaçışlanmadan ulaştığının işaretidir. Bunu yalnızca kendi sitenizde ve test ortamında deneyin.
- Günlüklerde: Erişim günlüğünde parametrelerde SQL anahtar sözcükleri içeren çok sayıda istek, hata günlüğünde art arda sözdizimi hataları, veritabanında olağandışı yavaş sorgular.
- Veride: Sizin eklemediğiniz yönetici hesapları, değişmiş içerikler, sayfalara eklenmiş yabancı bağlantılar.
PDO ile doğru bağlantı
Bağlantıyı kurarken üç ayar önemlidir: hataların istisna olarak fırlatılması, gerçek hazır ifadelerin kullanılması (öykünmenin kapatılması) ve karakter kümesinin bağlantı dizesinde belirtilmesi.
<?php
$pdo = new PDO(
'mysql:host=localhost;dbname=ornek_db;charset=utf8mb4',
$dbKullanici,
$dbParola,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
mysqli kullanan projelerde aynı ilke prepare ve bind_param ile uygulanır. Bir projede iki katmanı karıştırmak yerine mevcut olanı tutarlı kullanın.
Sık karşılaşılan özel durumlar
Sıralama ve kolon adı
Kullanıcının seçtiği sıralama alanını doğrudan sorguya koymayın; izin listesindeki sabit değerle eşleştirin.
<?php
// Kullanıcının seçimi -> sorguda kullanılacak sabit kolon adı
$siralamaAlanlari = [
'ad' => 'ad',
'fiyat' => 'fiyat',
'tarih' => 'eklenme_tarihi',
];
$secim = $_GET['sirala'] ?? 'ad';
$kolon = $siralamaAlanlari[$secim] ?? 'ad'; // listede yoksa varsayılan
$yon = (($_GET['yon'] ?? '') === 'azalan') ? 'DESC' : 'ASC'; // yalnız iki sabit değer
$sayfa = max(1, (int) ($_GET['sayfa'] ?? 1));
$adet = 20;
$bas = ($sayfa - 1) * $adet;
$sorgu = $pdo->prepare("SELECT id, ad, fiyat FROM urunler WHERE durum = ? ORDER BY $kolon $yon LIMIT ?, ?");
$sorgu->bindValue(1, 1, PDO::PARAM_INT);
$sorgu->bindValue(2, $bas, PDO::PARAM_INT);
$sorgu->bindValue(3, $adet, PDO::PARAM_INT);
$sorgu->execute();
LIKE ile arama
Arama terimi yine yer tutucuyla bağlanır. % ve _ karakterleri LIKE içinde joker anlamı taşıdığından, kullanıcının yazdığı bu karakterleri kaçışlamak beklenmeyen geniş eşleşmeleri önler.
<?php
$terim = trim((string) ($_GET['q'] ?? ''));
// LIKE jokerlerini (% ve _) ve kaçış karakterini etkisizleştir
$terim = addcslashes($terim, '%_\\');
$sorgu = $pdo->prepare('SELECT id, baslik FROM yazilar WHERE baslik LIKE ? LIMIT 50');
$sorgu->execute(['%' . $terim . '%']);
IN listesi
Değişken sayıda değer için eleman sayısı kadar yer tutucu üretin; değerleri yine bağlayarak gönderin.
<?php
$idler = array_map('intval', (array) ($_POST['idler'] ?? []));
$idler = array_values(array_filter($idler, fn ($id) => $id > 0));
if ($idler !== []) {
$yerTutucular = implode(',', array_fill(0, count($idler), '?'));
$sorgu = $pdo->prepare("SELECT id, ad FROM urunler WHERE id IN ($yerTutucular)");
$sorgu->execute($idler);
$urunler = $sorgu->fetchAll();
}
LIMIT ve sayısal değerler
Sayfa numarası gibi sayısal girdileri tam sayıya çevirip aralığını denetleyin. Tam sayıya çevirmek iyi bir doğrulamadır, ancak hazır ifadenin yerine geçmez; ikisini birlikte kullanın.
ORM ve sorgu oluşturucu kullanıyorsanız
Laravel, Symfony ve benzeri çerçevelerin ORM ve sorgu oluşturucuları, normal kullanımda değerleri kendiliğinden bağlar. Risk, "ham sorgu" yazılan yerlerde geri döner: whereRaw, orderByRaw, DB::raw ve benzeri yöntemlere kullanıcı girdisi birleştirilirse aynı açık oluşur. Ham sorgu gerekiyorsa bu yöntemlerin bağlama (binding) parametrelerini kullanın; sıralama alanları için yine izin listesi uygulayın.
En az yetkili veritabanı kullanıcısı
Web uygulaması veritabanına root ya da tüm yetkilere sahip bir kullanıcıyla bağlanmamalıdır. Uygulamaya özel bir kullanıcı oluşturun ve yalnızca kendi veritabanında, yalnızca gereken işlemlere izin verin. Şema değişikliği (tablo oluşturma, silme) gerektiren işler için ayrı bir kullanıcı kullanın.
-- Uygulamaya özel kullanıcı: yalnız kendi veritabanında, yalnız veri işlemleri
CREATE USER 'ornek_uyg'@'localhost' IDENTIFIED BY 'uzun-ve-benzersiz-bir-parola';
GRANT SELECT, INSERT, UPDATE, DELETE ON ornek_db.* TO 'ornek_uyg'@'localhost';
-- Verilmeyenler: DROP, ALTER, CREATE, FILE, GRANT OPTION ve diğer veritabanları
Paylaşımlı barındırmada kullanıcı ve yetkiler genellikle kontrol panelinden tanımlanır; ilke aynıdır: her site için ayrı veritabanı ve ayrı kullanıcı. Veritabanı sunucusu dış ağdan erişime kapalı olmalıdır.
Hata mesajlarını gizleyin, günlüğe yazın
Veritabanı hata mesajları tablo ve kolon adlarını, sorgunun yapısını ve dosya yollarını açığa çıkarır. Canlı ortamda hatalar ziyaretçiye gösterilmemeli, günlüğe yazılmalı ve kullanıcıya genel bir mesaj dönülmelidir.
<?php
try {
$sorgu = $pdo->prepare('SELECT id, ad FROM musteriler WHERE eposta = ?');
$sorgu->execute([$eposta]);
$musteri = $sorgu->fetch();
} catch (PDOException $hata) {
// Ayrıntı günlüğe; ziyaretçiye genel mesaj
error_log('Veritabanı hatası: ' . $hata->getMessage());
http_response_code(500);
echo 'İşlem şu anda gerçekleştirilemiyor. Lütfen daha sonra tekrar deneyin.';
return;
}
PHP ayarlarında da canlı ortam için display_errors = Off ve log_errors = On olmalıdır.
Yetersiz kalan yöntemler
- Kara liste: Girdiden belirli sözcükleri ya da karakterleri silmek güvenilir değildir; atlatılabilir ve meşru girdileri bozar.
addslashesve elle tırnak kaçışlama: Karakter kümesi ve bağlam farkları yüzünden güvenli değildir.- Yalnızca istemci tarafı doğrulama: Tarayıcıdaki JavaScript denetimleri kullanıcı deneyimi içindir; istek doğrudan sunucuya gönderilebilir.
- Yalnızca güvenlik duvarı (WAF): Yararlı bir ek katmandır, ancak koddaki açığı ortadan kaldırmaz.
- Saklı yordamın içinde metin birleştirmek: Saklı yordam kullanmak tek başına koruma sağlamaz; yordamın içinde dinamik SQL birleştiriliyorsa açık sürer.
Kontrol listesi
- Projede sorgu metnine değişken birleştirilen yer kalmadı.
- PDO bağlantısında istisna modu açık, öykünme kapalı, karakter kümesi
utf8mb4. - Sıralama, kolon ve tablo adları izin listesinden geliyor.
- Sayısal girdiler tam sayıya çevriliyor ve aralığı denetleniyor.
- Çerçevedeki ham sorgu yöntemleri gözden geçirildi.
- Uygulamanın veritabanı kullanıcısı yalnızca gereken yetkilere sahip; her site ayrı kullanıcı kullanıyor.
- Canlıda hata ayrıntısı ekrana basılmıyor, günlüğe yazılıyor.
- Veritabanı yedeği düzenli alınıyor.
Sık sorulan sorular
Hazır ifade kullanıyorsam başka bir şey yapmam gerekir mi?
Hazır ifade değerler için asıl korumadır. Sıralama ve kolon adı gibi bağlanamayan parçalar için izin listesi, ek katman olarak da en az yetkili veritabanı kullanıcısı ve hata mesajlarının gizlenmesi gerekir.
Girdiyi htmlspecialchars ile temizlemek SQL enjeksiyonunu önler mi?
Hayır. htmlspecialchars sayfaya çıktı basarken XSS'e karşı kullanılır; sorgu güvenliği için hazır ifade gerekir. Her önlem kendi bağlamında uygulanır.
Eski bir projede yüzlerce sorgu var; nereden başlamalıyım?
Önce oturum açmadan erişilebilen sayfalar ve giriş, arama, iletişim gibi formlar; sonra yönetim paneli. Kodda sorgu metnine değişken birleştirilen yerleri arayarak bir liste çıkarın ve her birini hazır ifadeye çevirin.
NoSQL veritabanı kullanıyorum; bu risk beni ilgilendirir mi?
Enjeksiyon yalnızca SQL'e özgü değildir. Kullanıcı girdisinin sorgu yapısına karıştığı her sistemde benzer risk vardır; kullandığınız veritabanının parametreli sorgu yöntemlerini kullanın ve girdi türlerini doğrulayın.
BYK Yazılım Destek Ekibi
Bu rehber BYK Yazılım destek ekibi tarafından hazırlanır ve düzenli olarak gözden geçirilir. Son güncelleme: 04.10.2026.
İlgili rehberler
Web sitenizi birlikte gözden geçirelim
BYK Yazılım kurumsal web siteleri geliştirir. Sitenizle ilgili sorularınız için bize yazabilirsiniz.
Bize ulaşın Kurumsal web sitesi hizmetimiz