Güvenlik

SQL enjeksiyonunu önleme

SQL enjeksiyonu ile tanışın, veritabanına yapılan en yaygın saldırı. Bu makalede parametreli sorgunun neden tek gerçek çözüm olduğunu öğrenecek ve PHP ile MySQL üzerinden pratik örneklerle sitenizin güvenliğini garanti altına alacaksınız.

Güvenlik

SQL Enjeksiyonu Nedir ve Neden Ciddiye Alınmalıdır?

SQL Enjeksiyonu (SQL Injection), web sitelerine yapılan en eski ve aynı zamanda en tehlikeli saldırılardan biridir. Bu saldırıda, saldırgan formlara, URL parametrelerine veya hatta HTTP başlıklarına özel girdiler göndererek kendi istediği SQL kodlarını veritabanı sorgularına enjekte eder. Eğer uygulamanız kullanıcı girdisini temizlemeden veya doğrulamadan doğrudan sorguya yerleştiriyorsa, saldırgan verileri okuyabilir, değiştirebilir, silebilir veya tüm veritabanının kontrolünü ele geçirebilir.

OWASP istatistiklerine göre, SQL enjeksiyonu yıllardır web güvenliği açısından en önemli on risk listesinde yer almaktadır. Sorgu yazarken yapılan küçük bir hata, kullanıcıların hassas bilgilerinin sızmasına, şifrelerin çalınmasına veya verilerin tamamen yok olmasına yol açabilir. Bu makalede, parametreli sorgunun (Parameterized Query) bu saldırıyla başa çıkmak için neden tek gerçek ve kesin çözüm olduğunu sade bir dille ve pratik örneklerle göstereceğiz.

SQL Enjeksiyonu Saldırı Mekanizmasını Tanıyın

Parametreli sorgunun önemini anlamak için önce saldırının nasıl çalıştığını görmeliyiz. Kullanıcının kullanıcı adı ve şifre girdiği basit bir giriş formunuz olduğunu varsayalım. PHP kodunuz şu şekilde olabilir:

<?php
$username = $_POST['username'];
$password = $_POST['password'];

$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);
?>

Şimdi, saldırgan kullanıcı adı alanına şu değeri girerse:

' OR '1'='1' -- 

Son sorgu şu şekle dönüşür:

SELECT * FROM users WHERE username = '' OR '1'='1' -- ' AND password = ''

SQL'de -- kısmı "satır sonuna kadar yorumdur" anlamına gelir, bu nedenle şifre koşulu tamamen göz ardı edilir. '1'='1' ifadesi her zaman doğrudur, bu yüzden sorgu users tablosundaki tüm kayıtları döndürür ve saldırgan şifreyi bilmeden hesaba giriş yapar. Bu, saldırının en basit türüdür; daha gelişmiş saldırılar verileri çıkarabilir, tabloları silebilir veya UNION SELECT aracılığıyla diğer tabloların bilgilerini okuyabilir.

Yaygın SQL Enjeksiyonu Türleri

  • In-band SQLi: Saldırgan sonucu doğrudan HTTP yanıtında görür. Error-based ve Union-based olmak üzere iki türü içerir.
  • Blind SQLi: Sonuç doğrudan gösterilmez, ancak saldırgan uygulamanın davranışındaki farklılıkları (örneğin zaman gecikmesi veya hata mesajı) gözlemleyerek bilgiyi tahmin eder.
  • Out-of-band SQLi: Veriler DNS veya HTTP isteği gibi başka bir kanal aracılığıyla saldırganın sunucusuna gönderilir.

Tüm bu türlerin ortak bir noktası vardır: uygulama, kullanıcı girdisini SQL komutunun bir parçası olarak yorumlar. Temel çözüm, "komut" ile "veri" arasında ayrım yapmaktır.

Geleneksel Savunma Yöntemleri Neden Yetersizdir?

Birçok geliştirici, SQL enjeksiyonunu önlemek için karakter kaçırma (Escaping) veya girdi filtreleme gibi yöntemler kullanır. Ancak bu yöntemler doğası gereği eksiktir ve defalarca başarısız olmuştur.

mysqli_real_escape_string ile İlgili Sorun

mysqli_real_escape_string işlevi, ', " ve \ gibi özel karakterleri ters eğik çizgiyle kaçırır. Ancak veritabanının karakter seti (Character Set) doğru ayarlanmamışsa, saldırgan GBK veya UTF-8 gibi hileler kullanarak bu kaçışı aşabilir. MySQL'de GBK karakter seti ile yapılan ünlü %bf%27 saldırısı, bu yöntemin tek başına güvenilir olmadığını gösterir.

Girdi Filtreleme ile İlgili Sorun

Bazı geliştiriciler SELECT, UNION veya DROP gibi anahtar kelimeleri girdiden kaldırmaya çalışır. Bu yöntem de başarısızlığa açıktır; saldırgan Encoding teknikleri, tekrarlanan boşluklar, yorumlar veya büyük/küçük harf kombinasyonları kullanarak filtreleri aşabilir. Örneğin, SeLeCt veya SELECT/**/FROM basit filtreleri kolayca geçer.

Önemli olan nokta, güvenliğin "kara liste" (Blacklist) üzerine değil, "beyaz liste" (Whitelist) ve komut ile verinin ayrılması üzerine kurulması gerektiğidir. Parametreli sorgu tam olarak bunu yapar.

Parametreli Sorgu Nedir ve Nasıl Çalışır?

Parametreli Sorgu (Parameterized Query) veya Prepared Statement, SQL sorgusunun yapısının önce tanımlandığı ve ardından değerlerin ayrı ayrı ve placeholder kullanılarak gönderildiği bir yöntemdir. Veritabanı bu değerleri "veri" olarak yorumlar, "komut"un bir parçası olarak değil. Bu doğal ayrım, SQL enjeksiyonunu teknik olarak imkansız hale getirir.

PHP ve MySQLi ile Pratik Örnek

Aynı giriş formu örneğini parametreli sorguyla yeniden yazalım:

<?php
$username = $_POST['username'];
$password = $_POST['password'];

// placeholder ile sorgunun hazırlanması
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$result = $stmt->get_result();
?>

Bu kodda, ? işareti placeholder olarak çalışır. bind_param işlevi veri türünü (burada string için s) ve değeri belirtir. MySQL veritabanı bu değerleri ham veri olarak alır ve asla SQL komutu olarak çalıştırmaz. Saldırgan ' OR '1'='1' -- değerini girse bile, bu dize username alanı için normal bir değer olarak kabul edilir ve sorgunun yapısı üzerinde hiçbir etkisi olmaz.

PDO (PHP Data Objects) ile Örnek

PDO, veritabanıyla çalışmak için daha modern bir yöntemdir ve parametreli sorguyu standart olarak destekler:

<?php
$pdo = new PDO("mysql:host=localhost;dbname=mydb;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->execute(['username' => $_POST['username'], 'password' => $_POST['password']]);
$user = $stmt->fetch();
?>

Burada kodun okunabilirliğini artıran adlandırılmış placeholder'lar (:username ve :password) kullanılmıştır. execute işlevi değerleri dizi olarak alır ve PDO bunları otomatik olarak veritabanına doğru şekilde gönderir.

Diğer Dillerde ve Çerçevelerde Parametreli Sorgu

Bu desen yalnızca PHP ile sınırlı değildir. Tüm modern dillerde ve çerçevelerde parametreli sorgu, güvenliğin altın standardı olarak kabul edilir.

  • Python (psycopg2 veya sqlite3 ile): placeholder olarak %s veya ? kullanın.
  • Node.js (mysql2 veya pg ile): ? veya $1 kullanın.
  • Java (JDBC ile): PreparedStatement kullanın.
  • Laravel, Django, Rails gibi çerçeveler: Varsayılan olarak parametreli sorguyu otomatik olarak uygulayan Query Builder veya ORM kullanır.

Bir çerçeve kullanıyorsanız, bunun için neredeyse her zaman hazır yöntemler vardır. Örneğin Laravel'de, DB::select('SELECT * FROM users WHERE id = ?', [$id]) otomatik olarak parametreli sorgu kullanır.

Yaygın Hatalar ve Sorun Giderme İpuçları

Parametreli sorgu kullansanız bile, güvenliği tehlikeye atabilecek hatalar oluşabilir. İşte birkaç yaygın durumu inceliyoruz.

Hata: Parametreli Sorguyu Yalnızca Sorgunun Bir Kısmı İçin Kullanmak

Bazı geliştiriciler yalnızca WHERE değerlerini parametreleştirir, ancak tablo veya sütun adını doğrudan kullanıcı girdisinden oluşturur. Şu örneğe dikkat edin:

<?php
$table = $_GET['table']; // kullanıcı girdisi
$stmt = $pdo->prepare("SELECT * FROM $table WHERE id = ?");
$stmt->execute([$id]);
?>

Burada saldırgan table değerini users; DROP TABLE users; -- olarak değiştirebilir. Parametreli sorgu yalnızca değerler için çalışır, tanımlayıcılar (Identifiers) için değil. Tablo ve sütun adları için beyaz liste kullanmalısınız:

<?php
$allowedTables = ['users', 'products', 'orders'];
if (!in_array($table, $allowedTables)) {
    die("Geçersiz tablo adı");
}
?>

Hata: Karakter Setini Ayarlamayı Unutmak

Veritabanı bağlantısının karakter setini doğru ayarlamazsanız, parametreli sorgu bile bazı durumlarda savunmasız olabilir. Her zaman utf8mb4 kullanın ve PDO'da bunu DSN'de ayarlayın:

$pdo = new PDO("mysql:host=localhost;dbname=mydb;charset=utf8mb4", $user, $pass);

Hata: ORM'ye İşleyişini Anlamadan Güvenmek

Birçok ORM, ham sorgu yazmanıza izin verir. Bu özelliği kullanıyorsanız, parametreli sorgu kurallarına uymalısınız. Örneğin Laravel'de whereRaw yöntemi ham sorgu yazmanıza izin verir; kullanıcı girdisini doğrudan içine yerleştirirseniz, SQL enjeksiyonu riski aynıdır.

Tamamlayıcı Savunma Katmanları

Parametreli sorgu savunmanın ön cephesidir, ancak derinlemesine savunma (Defense in Depth) başka katmanlara da sahip olmanızı gerektirir.

  • Girdi doğrulama: Parametreli sorguyla bile girdileri tür, uzunluk ve biçim açısından kontrol edin. Örneğin, e-posta alanı regex ile doğrulanmalıdır.
  • Veritabanı minimum erişimi: Uygulamanın veritabanı kullanıcısı DROP veya ALTER erişimine sahip olmamalıdır. Yalnızca gerekli erişimleri (SELECT, INSERT, UPDATE, DELETE) verin.
  • Hassas verilerin şifrelenmesi: Şifreleri password_hash ile hash'leyin ve asla düz metin olarak saklamayın.
  • Günlükleme ve izleme: SQL enjeksiyonu için şüpheli girişimleri günlüklere kaydedin ve uyarı alın.

Özet: Parametreli Sorguyu Alışkanlık Haline Getirin

SQL enjeksiyonu, her yıl milyonlarca web sitesini etkileyen ciddi bir tehdittir. Karakter kaçırma ve girdi filtreleme gibi geleneksel yöntemler geçici ve eksik çözümlerdir. Tek gerçek ve kesin çözüm, komut ile veri arasında doğal bir ayrım sağlayan parametreli sorgu (Prepared Statement) kullanmaktır.

Bu deseni uygulamanızın tüm katmanlarında uygulayın: giriş formları, arama, filtreler, sıralama ve veritabanıyla etkileşimde olduğunuz her yer. Bir çerçeve kullanıyorsanız, dahili özelliklerinden yararlanın ve ham kod yazıyorsanız, her zaman Prepared Statement kullanın. Bunu yaparak, web sitenizi güvence altına almak için en önemli adımlardan birini atmış olursunuz.

Web siteniz için güvenli bir altyapı arıyorsanız, ServerNet, güvenliğe odaklanan web hosting ve bulut sunucu hizmetleri sunar ve güvenli uygulamalar çalıştırmanıza yardımcı olabilir.

ServerNet Destek

ServerNet mühendislik ve yayın ekibi — altyapı, ağ ve web barındırma uzmanları.

Güvenlik Hizmetleri
Paylaş:

Yorumlar 0

Henüz yorum yok — ilk siz olun!

Yorum bırakın

İlgili hizmet

Güvenlik Hizmetleri

OSCP sertifikalı uzmanlarla sızma testi, altyapı sıkılaştırma ve 7/24 güvenlik izleme.