PHP'de SQL enjeksiyonunu nasıl önleyebilirim?

Kullanıcı girdisi değiştirilmeden bir SQL sorgusuna eklenirse, uygulama aşağıdaki örnekte olduğu gibi SQL enjeksiyonuna karşı savunmasız hale gelir:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

Bunun nedeni, kullanıcının value'); DROP TABLE table;-- gibi bir şey girebilmesi ve sorgunun bu hale gelmesidir:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

Bunun olmasını önlemek için ne yapılabilir?

Deprecated Warning: Bu yanıtın örnek kodu (sorunun örnek kodu gibi) PHP 5.5.0'da kullanımdan kaldırılan ve PHP 7.0.0'da tamamen kaldırılan PHP'nin `MySQL' eklentisini kullanmaktadır.

Güvenlik Uyarısı: Bu cevap en iyi güvenlik uygulamaları ile uyumlu değildir. Kaçış SQL enjeksiyonunu önlemek için yetersizdir, bunun yerine hazırlanmış ifadeler kullanın. Aşağıda özetlenen stratejiyi kendi sorumluluğunuzda kullanın. (Ayrıca, mysql_real_escape_string() PHP 7'de kaldırılmıştır).

PHP'nin yeni bir sürümünü kullanıyorsanız, aşağıda özetlenen mysql_real_escape_string seçeneği artık kullanılamayacaktır (ancak mysqli::escape_string modern bir eşdeğerdir). Bugünlerde mysql_real_escape_string seçeneği yalnızca PHP'nin eski bir sürümündeki eski kodlar için mantıklı olacaktır.


İki seçeneğiniz var: unsafe_variable içindeki özel karakterlerden kaçmak ya da parametrelendirilmiş bir sorgu kullanmak. Her ikisi de sizi SQL enjeksiyonundan koruyacaktır. Parametrelendirilmiş sorgu daha iyi bir uygulama olarak kabul edilir, ancak kullanabilmeniz için PHP'de daha yeni bir MySQL eklentisine geçmeniz gerekecektir.

Önce daha düşük etkili ip kaçışını ele alacağız.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

Ayrıca, mysql_real_escape_string işlevinin ayrıntılarına da bakın.

Parametrelendirilmiş sorguyu kullanmak için MySQL işlevleri yerine MySQLi işlevlerini kullanmanız gerekir. Örneğinizi yeniden yazmak için aşağıdaki gibi bir şeye ihtiyacımız olacaktır.

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

Burada okumak isteyeceğiniz anahtar fonksiyon mysqli::prepare olacaktır.

Ayrıca, diğerlerinin de önerdiği gibi, PDO gibi bir şeyle bir soyutlama katmanı yükseltmeyi daha yararlı/kolay bulabilirsiniz.

Lütfen sorduğunuz vakanın oldukça basit bir vaka olduğunu ve daha karmaşık vakaların daha karmaşık yaklaşımlar gerektirebileceğini unutmayın. Özellikle de:

  • SQL'in yapısını kullanıcı girdisine göre değiştirmek istiyorsanız, parametrelendirilmiş sorgular yardımcı olmayacaktır ve gereken kaçış mysql_real_escape_string tarafından kapsanmamaktadır. Bu tür bir durumda, yalnızca 'güvenli' değerlere izin verildiğinden emin olmak için kullanıcı girdisini bir beyaz listeden geçirmeniz daha iyi olacaktır.
  • Bir koşulda kullanıcı girdisinden tam sayılar kullanırsanız ve mysql_real_escape_string yaklaşımını benimserseniz, aşağıdaki yorumlarda Polynomial tarafından açıklanan sorundan muzdarip olursunuz. Bu durum daha zordur çünkü tamsayılar tırnak işaretleriyle çevrelenmeyecektir, bu nedenle kullanıcı girdisinin yalnızca rakam içerdiğini doğrulayarak başa çıkabilirsiniz.
  • Muhtemelen farkında olmadığım başka durumlar da vardır. Bu]7, karşılaşabileceğiniz daha ince sorunların bazıları hakkında yararlı bir kaynak olabilir.
Yorumlar (4)

Parametrelendirilmiş SQL sorgularını çalıştırmak için PDO (PHP Data Objects) kullanmanızı tavsiye ederim.

Bu sadece SQL enjeksiyonuna karşı koruma sağlamakla kalmaz, aynı zamanda sorguları da hızlandırır.

Ve mysql_, mysqli_ ve pgsql_ fonksiyonları yerine PDO kullanarak, veritabanı sağlayıcılarını değiştirmeniz gereken nadir durumlarda uygulamanızı veritabanından biraz daha soyutlamış olursunuz.

Yorumlar (3)

Bunun gibi basit bir şey yapabilirsiniz:

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Bu her sorunu çözmeyecektir, ancak çok iyi bir atlama taşıdır. Değişkenin varlığını, biçimini (sayılar, harfler, vb.) kontrol etmek gibi bariz öğeleri dışarıda bıraktım.

Yorumlar (10)