Dijital Kalenizin Kapılarını Kimlere Açıyorsunuz? SQL Injection Gerçeği
Manavgat’ın o meşhur, insanı bazen bunaltan ama akşamları serinleyen havasında, ofiste bilgisayarımın başında otururken sıkça düşündüğüm bir şey var: Güvenlik. Nasıl ki ormana kampa gittiğimde çadırımı kuracağım yeri özenle seçiyor, gece olası ziyaretçilere (domuz sürülerine mesela) karşı önlemlerimi alıyorsam; yazdığım kodun da internetin o vahşi ormanında hayatta kalabilmesi için aynı özeni göstermek zorundayım. 2005 yılında bu işlere ilk başladığımda, dürüst olayım, "çalışsın da nasıl çalışırsa çalışsın" mantığındaydım. Ama Pixel Lab®’ı kurup, insanların işlerini emanet ettiği sistemler geliştirmeye başladığımda anladım ki; güvenlik bir özellik değil, bir zorunluluk.
Bugün, web dünyasının en eski ama hala en can yakan belalarından birini, SQL Injection (SQLi) konusunu masaya yatırmak istiyorum. Ama öyle akademik, sıkıcı tanımlarla değil; mutfaktan, işin içinden biri olarak, neden hala bu açığı verdiğimizi ve modern PHP ile (özellikle PDO kullanarak) bu kapıyı nasıl sıkı sıkıya kilitleyeceğimizi konuşacağız.
Eski Defterler ve "admin' --" Efsanesi
Yazılıma başladığım ilk yılları hatırlıyorum. O zamanlar PHP’de mysql_query fonksiyonu vardı (artık tarihe gömüldü, huzur içinde uyumasın). Kullanıcıdan gelen veriyi doğrudan sorgunun içine yapıştırırdık. Bir giriş formu düşünün; kullanıcı adı ve şifre istiyor. Arka planda kod şöyle bir şeye benziyordu:
// 2005 yılından kalma, ASLA YAPMAMANIZ GEREKEN bir örnek
$sql = "SELECT * FROM users WHERE username = '" . $username . "' AND password = '" . $password . "'";
Buradaki mantık hatası o kadar büyük ki... Biz yazılımcı olarak kullanıcının "uslu" duracağını ve sadece ismini yazacağını varsayıyoruz. Ama dünya böyle bir yer değil. Eğer kullanıcı, ad kısmına admin' -- yazarsa ne olur? SQL sorgusu şuna dönüşür:
SELECT * FROM users WHERE username = 'admin' --' AND password = '...'
SQL dilinde -- işareti, satırın geri kalanını yorum satırı (comment) yapar. Yani veritabanı şifre kontrolünü tamamen görmezden gelir ve sizi "admin" olarak içeri alır. İşte bu, en basit haliyle bir SQL Enjeksiyonudur. Bir hırsıza anahtar sormak yerine, hırsızın kapı kilidini söküp içeri girmesi gibidir.
Yıllar içinde bu açıklar yüzünden milyonlarca veri çalındı, şirketler battı. Peki, biz Pixel Lab® projelerinde bu riski nasıl sıfıra indiriyoruz? Cevap: Veriye asla güvenmeyerek ve teknolojiyi doğru kullanarak.
Modern Savunma Hattı: PDO ve Prepared Statements
Artık mysql_ fonksiyonları yok. Hatta mysqli bile bazen yetersiz kalabiliyor veya karmaşıklaşabiliyor. Benim tercihim ve endüstri standardı olan yapı PDO (PHP Data Objects). PDO kullanmanın en büyük avantajı, veritabanı bağımsız çalışabilmesi değil, sağladığı Prepared Statements (Hazırlanmış İfadeler) yapısıdır.
Prepared Statements’ın mantığını şöyle anlatayım: Diyelim ki bir devlet dairesine dilekçe vereceksiniz. Eski sistemde (SQLi açığı olan), boş bir kağıda ne isterseniz yazıp memura veriyordunuz. Memur da okumadan işleme koyuyordu. Prepared Statements ise matbu bir form gibidir. Formun şablonu bellidir, kutucukların yeri bellidir. Siz o kutucuğun içine "Bu binayı yıkın" yazsanız bile, memur onu sadece bir "isim" veya "adres" verisi olarak görür, emir olarak algılamaz.
Kodun Mimarisi Nasıl Değişiyor?
Gelin, yukarıdaki o felaket kodu modern ve güvenli hale getirelim. Burada veriyi ve sorgu yapısını birbirinden ayırıyoruz.
// Veritabanı bağlantımızı PDO ile kurduğumuzu varsayıyorum
$pdo = new PDO($dsn, $user, $pass);
// 1. Adım: Sorgu Şablonunu Hazırla (Prepare)
// Dikkat: Değişkenleri doğrudan sorguya yazmıyoruz, yer tutucu (?) veya isimlendirilmiş parametre (:username) kullanıyoruz.
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
// 2. Adım: Veriyi Bağla ve Çalıştır (Execute)
// Kullanıcı ne girerse girsin, bu sadece bir 'veri'dir, SQL komutu değildir.
$stmt->execute([
'username' => $inputUsername,
'password' => $inputPassword
]);
$user = $stmt->fetch();
Bu yapıda veritabanı sunucusu önce SQL sorgusunun yapısını (SELECT, WHERE vs.) derler. Daha sonra gönderdiğimiz verileri bu yapının içindeki boşluklara yerleştirir. Kullanıcı ' OR '1'='1 gibi sinsi kodlar gönderse bile, veritabanı bunu bir SQL komutu olarak değil, sadece garip bir kullanıcı adı metni olarak işler. Sonuç? Saldırı başarısız.
Sadece Kodlamak Yetmez: Veriyi Sterilize Etmek
Prepared Statements, SQL Injection’a karşı %99 koruma sağlar. Ama ben detaycı biriyim. Kod yazarken de, doğada kamp yaparken de "acaba"lara yer bırakmayı sevmem. Bu yüzden "Input Validation" (Girdi Doğrulama) ve "Sanitization" (Sterilize Etme) kavramlarını da sürece dahil etmek zorundayız.
Bir e-posta adresi istiyorsak, gelen verinin gerçekten e-posta formatında olup olmadığını kontrol etmeliyiz. Ya da bir ID değeri bekliyorsak, bunun bir tamsayı (integer) olduğundan emin olmalıyız.
$email = $_POST['email'];
// PHP'nin yerleşik filtreleri hayat kurtarır
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
// Bu bir e-posta değil! İşlemi durdur, hata ver.
die("Geçersiz e-posta formatı.");
}
// Veya bir ID alıyorsak
$id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
Bu yaklaşım, veritabanına giden yolda ikinci bir kontrol kapısı oluşturur. Sadece kapıyı kilitlemekle kalmaz, kapıya gelenin kimliğine de bakmış olursunuz. Temiz kod, sadece çalışan kod değildir; neyi işlediğini bilen koddur.
En Az Yetki Prensibi: Zararı Minimize Etmek
Bazen en iyi yazılımcı bile hata yapabilir veya kullandığınız bir kütüphanede açık çıkabilir. Bu noktada "Least Privilege" (En Az Yetki) prensibi devreye girer. Uygulamanızın veritabanına bağlandığı kullanıcının yetkileri neler?
Web sitenizdeki veritabanı kullanıcısının DROP TABLE yetkisine ihtiyacı var mı? Muhtemelen hayır. Sadece veri okuyacaksa (SELECT), veri ekleyecekse (INSERT) ve güncelleyecekse (UPDATE); sadece bu yetkileri verin. Eğer bir şekilde saldırgan sızmayı başarırsa, en azından tüm veritabanını silmesini engellemiş olursunuz.
- Asla uygulamanızı 'root' veya 'sa' gibi süper yetkili kullanıcılarla bağlamayın.
- Her uygulama için ayrı veritabanı kullanıcısı oluşturun.
- Gereksiz yetkileri (FILE, GRANT, DROP vb.) iptal edin.
Güvenlik, Bisiklete Binmek Gibidir
Yazının başında dediğim gibi, ben hayatımı denge üzerine kurdum. Ormanda bisiklet sürerken kaskımı takarım, rotamı kontrol ederim, hava durumuna bakarım. Bunlar sürüş keyfimi kaçırmaz, aksine güvenle pedallamamı sağlar. Kod yazarken de güvenlik önlemleri almak, Prepared Statements kullanmak, veriyi doğrulamak size zaman kaybettirmez. Aksine, gece yastığa başınızı koyduğunuzda rahat uyumanızı sağlar.
Manavgat’taki ofisimde, Pixel Lab® projelerini geliştirirken her zaman bu disiplinle hareket ediyorum. Çünkü biliyorum ki, basit ve sade bir sistem, ancak güvenli olduğunda gerçekten "çalışıyor" demektir. Amatör ruhla çektiğim belgesellerde doğanın vahşiliğini izlemek keyifli olabilir ama kendi sunucularımda o vahşiliği görmek istediğim son şey.
Kodlarınız temiz, veritabanınız kapalı kutu, mantığınız açık olsun. Bir sonraki yazıda görüşmek üzere.
Yorumlar
Henüz yorum yapılmamış. İlk yorumu siz yazın!
Yorum Yaz