- Konu sahibi
- #1
SQL injection saldırılarını önlemenin temel yolu, kullanıcıdan gelen değerleri SQL metnine birleştirmemek ve veritabanı sürücüsünün desteklediği parametreli sorguları kullanmaktır. Bunun yanında tablo ve sütun adları gibi sorgu yapısının parçalarını yalnızca izin verilen sabit seçeneklerden seçmeli, uygulama hesabına gereksiz veritabanı yetkileri vermemeli ve girişleri beklenen veri türüne göre doğrulamalısınız. Bu önlemler birlikte uygulandığında saldırganın gönderdiği metin, çalıştırılacak SQL komutu yerine yalnızca veri olarak değerlendirilir.
Aşağıdaki yaklaşım tehlikelidir; çünkü
Kullanıcı girdisi, sorgunun tırnaklarını veya yapısını değiştirirse veritabanı uygulamanın amaçladığından farklı bir komut çalıştırabilir. Girdinin formdan, çerezden, gizli alandan veya açılır listeden gelmesi bunu güvenli hâle getirmez; istemciden gelen her değer güvenilmeyen veri olarak ele alınmalıdır.
Örneğin kullanıcı e-postasına göre kayıt arayan PHP ve PDO kodunda sorgu şablonunu değerlerden ayırın:
Burada
Kodunuzda
Aynı kural bütün sorgu türleri için geçerlidir. Örneğin kayıt eklerken:
Sorgu başarıyla çalışırsa yeni kayıt eklenir; hata oluşursa uygulamanın ayrıntılı veritabanı hatasını ziyaretçiye göstermeyin. Hatayı sunucu tarafında günlükleyin, kullanıcıya ise işlemin başarısız olduğunu belirten genel bir yanıt verin. Günlüklerde parola, oturum belirteci veya gereksiz kişisel veri bırakmayın.
Parametreler veri değerlerini temsil eder; tablo adı, sütun adı, SQL anahtar sözcüğü veya sorgunun keyfî bir bölümü yerine kullanılamaz. Bu nedenle sıralama alanını doğrudan istekten alıp sorguya eklemek güvenli değildir:
Bunun yerine dışarıdan gelen seçeneği sabit bir izin listesinde eşleyin:
Burada sorguya eklenen değer, kullanıcının gönderdiği ham metin değil, uygulamada önceden tanımlanmış sütun adlarından biridir. Sıralama yönü gibi seçeneklerde de aynı yaklaşımı kullanın: yalnızca
Bu ayrım önemlidir: Parametreli sorgu, veri değerlerinin SQL komutuna dönüşmesini engeller; yanlış seçilmiş bir tablo adı veya sütun adı için tek başına çözüm değildir.
Parametreli sorgu ana savunmadır; doğrulama ise uygulamanın beklenmeyen verilerle çalışmasını önleyen ek katmandır. Kimlik alanı bekleniyorsa gerçekten izin verilen biçimde bir kimlik alın, sayısal olmayan değeri işlemden önce reddedin. E-posta alanında biçim kontrolü yapın; ancak biçim kontrolünü SQL injection korumasının yerine koymayın. Geçerli görünen bir değer de sorguya doğrudan birleştirilirse risk devam eder.
Uygulama veritabanına yönetici veya veritabanı sahibi hesabıyla bağlanmamalıdır. Kullanılan hesap yalnızca uygulamanın ihtiyaç duyduğu tablo ve işlemlere erişebilmelidir. Böylece bir sorgu açığı oluşsa bile saldırganın etkileyebileceği alan daralır. Bu, açığı ortadan kaldırmaz; yalnızca ek zarar sınırlaması sağlar.
Önce uygulamanın veritabanına eriştiği tüm dosya ve sınıfları çıkarın. Özellikle
Bir test sırasında uygulamanın gerçek verilerini değiştirmeyin veya silmeyin. Üretim ortamında saldırı dizeleri denemek yerine izole bir kopya, test veritabanı ve yedek kullanın. Şüpheli bir açıklık bulunursa ilgili uç noktanın adresini, etkilenen parametreyi, uygulama ve veritabanı sürümünü, hata zamanını ve sunucu günlüklerindeki ilgili kayıtları güvenli biçimde saklayın; parolaları ve oturum bilgilerini paylaşmadan düzeltme talebi oluşturun. Açık kapatıldıktan sonra aynı kod yolunun bütün sorgu türlerinde parametreli çalıştığını yeniden inceleyin.
Kullanıcı girdisi sorguya nasıl eklenmemeli?
Aşağıdaki yaklaşım tehlikelidir; çünkü
$email değişkeni doğrudan SQL metnine eklenmektedir:
PHP:
$email = $_POST['email'];
$sql = "SELECT id, name FROM users WHERE email = '$email'";
$result = $pdo->query($sql);
Kullanıcı girdisi, sorgunun tırnaklarını veya yapısını değiştirirse veritabanı uygulamanın amaçladığından farklı bir komut çalıştırabilir. Girdinin formdan, çerezden, gizli alandan veya açılır listeden gelmesi bunu güvenli hâle getirmez; istemciden gelen her değer güvenilmeyen veri olarak ele alınmalıdır.
Parametreli sorguya geçiş
Örneğin kullanıcı e-postasına göre kayıt arayan PHP ve PDO kodunda sorgu şablonunu değerlerden ayırın:
PHP:
$email = $_POST['email'] ?? '';
$sql = 'SELECT id, name FROM users WHERE email = :email';
$stmt = $pdo->prepare($sql);
$stmt->execute(['email' => $email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
Burada
:email bir yer tutucudur. E-postanın gerçek değeri SQL metnine birleştirilmez; execute() sırasında ayrı parametre olarak gönderilir. Kullanıcı ' OR '1'='1 gibi bir metin gönderse bile bu değer, sorgunun koşulunu değiştiren SQL parçası değil, aranacak tek bir veri değeri olarak işlenmelidir.Kodunuzda
prepare() kullanıp kullanıcı değerini yine de sorgu metninin içine yazarsanız koruma sağlamış olmazsınız. Her dinamik değer için ayrı bir yer tutucu kullanın ve değerleri execute(), bindValue() veya kullandığınız kütüphanenin eşdeğer parametre bağlama yöntemiyle gönderin.Arama, ekleme, güncelleme ve silme sorguları
Aynı kural bütün sorgu türleri için geçerlidir. Örneğin kayıt eklerken:
PHP:
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$stmt = $pdo->prepare(
'INSERT INTO users (name, email) VALUES (:name, :email)'
);
$stmt->execute([
'name' => $name,
'email' => $email,
]);
Sorgu başarıyla çalışırsa yeni kayıt eklenir; hata oluşursa uygulamanın ayrıntılı veritabanı hatasını ziyaretçiye göstermeyin. Hatayı sunucu tarafında günlükleyin, kullanıcıya ise işlemin başarısız olduğunu belirten genel bir yanıt verin. Günlüklerde parola, oturum belirteci veya gereksiz kişisel veri bırakmayın.
Tablo ve sütun adları için farklı yöntem gerekir
Parametreler veri değerlerini temsil eder; tablo adı, sütun adı, SQL anahtar sözcüğü veya sorgunun keyfî bir bölümü yerine kullanılamaz. Bu nedenle sıralama alanını doğrudan istekten alıp sorguya eklemek güvenli değildir:
PHP:
$sort = $_GET['sort'] ?? 'name';
$sql = "SELECT id, name, email FROM users ORDER BY $sort";
Bunun yerine dışarıdan gelen seçeneği sabit bir izin listesinde eşleyin:
PHP:
$sortInput = $_GET['sort'] ?? 'name';
$allowedSorts = [
'name' => 'name',
'newest' => 'created_at',
];
$sortColumn = $allowedSorts[$sortInput] ?? 'name';
$stmt = $pdo->query(
"SELECT id, name, email FROM users ORDER BY $sortColumn"
);
Burada sorguya eklenen değer, kullanıcının gönderdiği ham metin değil, uygulamada önceden tanımlanmış sütun adlarından biridir. Sıralama yönü gibi seçeneklerde de aynı yaklaşımı kullanın: yalnızca
ASC veya DESC gibi izin verilen değerlerden birini seçin. İzin listesi yoksa ilgili özelliği kullanıcı girdisiyle dinamik hâle getirmeyin.| Sorgu bölümü | Güvenli yaklaşım |
|---|---|
| Değerler: arama metni, e-posta, kimlik | Parametreli sorgu ve bağlı değer |
| Tablo veya sütun adı | Sabit seçim ya da izin listesi eşlemesi |
| Sıralama yönü | Önceden belirlenmiş ASC/DESC seçenekleri |
| Sayısal kimlik | Beklenen veri türü doğrulaması ve parametreli sorgu |
Girdi doğrulama ve veritabanı yetkileri
Parametreli sorgu ana savunmadır; doğrulama ise uygulamanın beklenmeyen verilerle çalışmasını önleyen ek katmandır. Kimlik alanı bekleniyorsa gerçekten izin verilen biçimde bir kimlik alın, sayısal olmayan değeri işlemden önce reddedin. E-posta alanında biçim kontrolü yapın; ancak biçim kontrolünü SQL injection korumasının yerine koymayın. Geçerli görünen bir değer de sorguya doğrudan birleştirilirse risk devam eder.
Uygulama veritabanına yönetici veya veritabanı sahibi hesabıyla bağlanmamalıdır. Kullanılan hesap yalnızca uygulamanın ihtiyaç duyduğu tablo ve işlemlere erişebilmelidir. Böylece bir sorgu açığı oluşsa bile saldırganın etkileyebileceği alan daralır. Bu, açığı ortadan kaldırmaz; yalnızca ek zarar sınırlaması sağlar.
Mevcut kod nasıl denetlenir?
Önce uygulamanın veritabanına eriştiği tüm dosya ve sınıfları çıkarın. Özellikle
query(), exec(), mysqli_query(), SQL birleştirme işlemleri ve değişken içeren sorgu dizelerini arayın. Her bulguda şu sırayı izleyin:- Sorguya giren değerin kaynağını belirleyin: form, URL, çerez, başlık, dosya veya başka bir servis.
- Değer SQL metnine birleştiriliyorsa sorguyu sabit şablon ve parametreler kullanacak şekilde yeniden yazın.
- Tablo, sütun veya sıralama gibi yapısal bir bölüm dinamikse ham değeri kaldırıp izin listesi eşlemesi ekleyin.
- Bağlanan veritabanı hesabının gerçekten ihtiyaç duyduğu yetkileri kontrol edin.
- Değişiklikten sonra normal geçerli girdilerle ve reddedilmesi gereken hatalı girdilerle uygulamanın beklenen davranışını sınayın.
Bir test sırasında uygulamanın gerçek verilerini değiştirmeyin veya silmeyin. Üretim ortamında saldırı dizeleri denemek yerine izole bir kopya, test veritabanı ve yedek kullanın. Şüpheli bir açıklık bulunursa ilgili uç noktanın adresini, etkilenen parametreyi, uygulama ve veritabanı sürümünü, hata zamanını ve sunucu günlüklerindeki ilgili kayıtları güvenli biçimde saklayın; parolaları ve oturum bilgilerini paylaşmadan düzeltme talebi oluşturun. Açık kapatıldıktan sonra aynı kod yolunun bütün sorgu türlerinde parametreli çalıştığını yeniden inceleyin.