Kullanılan Araçlar :
PHP 5.4.6
Linux Mint
Sublime Text
Zap proxy
PHP uygulamalarının büyük bir bölümü veritabanı işlemlerini mysql_* fonksiyonları üzerinden gerçekleştirmektedir. Bununla ilgili Github içinde ufak bir arama gerçekleştirdik.
Github dork : "extension:php mysql_query"
Yaklaşık 316 bin kod parçasında mysql_query() fonksiyonu kullanılıyor.
Bu hatırı sayılır bir rakam olmasına rağmen php geliştiricileri farklı düşünüyor:
This extension is deprecated as of PHP 5.5.0, and will be removed in the future. Instead, the MySQLi or PDO_MySQLextension should be used. See also MySQL: choosing an API guide and related FAQ for more information. Alternatives to this function include:
Kaynak : http://tr1.php.net/mysql_query
Gördüğümüz kadarıyla PHP 5.5.0 ile birlikte mysql_query fonksiyonu tedavülden kalkacak ve PDO yapısına geçmek zorunda kalacağız. mysql_query kullanan geliştiriciler için kodlarını yenilemek meşakkatli olacak gibi. Bardağın dolu tarafından bakalım. Kısa bir zaman önce öğrenmeye başladığım Laravel4 dökümantasyonunda şöyle bir ifade geçiyor:
Not: Laravel sorgu oluşturucusu, uygulamanızı SQL enjeksiyon saldırılarına karşı korumak için PDO parametre bağlayıcı kullanmaktadır. Bağlayıcı olarak geçirilen yazıların temizlenmesine gerek yoktur.
Bir Çatı(Framework) kullanmak, SQL enjeksiyon gibi bir zaafiyete karşı kesin çözüm gibi duruyor. Çatı(Framework) kullan ve güvende ol! Bu yaklaşımın büyük ölçüde doğru olduğunu düşünüyorum. Ama PDO extension'ı kendi sınıf yapısıyla deneyimlemek isteyen bir geliştirici için ufak bir mantık hatası, büyük sıkıntıları da beraberinde getirebilir. Aşağıdaki örnekte ufak bir PDO bağlantı sınıfı ve SQL enjeksiyon zafiyeti içeren bir kod parçası yazdım. Satır satır inceleyelim:
PDO bağlantısını sağlayan statik sınıf:
class DB {
protected static $instance;
protected function __construct() {}
public static function getInstance() {
if(empty(self::$instance)) {
$db_info = array(
"db_host" => "localhost",
"db_port" => "3306",
"db_user" => "root",
"db_pass" => "",
"db_name" => "",
"db_charset" => "UTF-8"
);
try {
self::$instance = new PDO("mysql:host=".$db_info['db_host'].';port='.$db_info['db_port'].';dbname='.$db_info['db_name'], $db_info['db_user'], $db_info['db_pass']);
//self::$instance = new PDO("mysql:host=localhost;, $db_info['db_user'], $db_info['db_pass']);
#self::$instance->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
self::$instance->setAttribute( PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true);
self::$instance->query('SET NAMES utf8');
self::$instance->query('SET CHARACTER SET utf8');
}
catch(PDOException $e) {
echo $e->getMessage();
}
}
return self::$instance;
}
}
Zaafiyete neden olan kod parçası :
//user-agent bilgisi kullanıcıdan alınıyor. $var = $_SERVER['HTTP_USER_AGENT']; // DB sınıfı çağrılıyor. $connection = DB::getInstance(); //SQL sorgusu yazılıyor. $query = "INSERT INTO `authors`(`name`, `bio`) VALUES ( 'one', $var )"; //SQL sorgusu işletiliyor. $result = $connection->prepare($query); $result->execute(); echo "Kayıt girildi...!\n";
Kodu sömürmek için saldırı vektörümüz şu şekilde olmalıdır:
(SELECT 'TEST' REGEXP (SELECT CASE WHEN (substr(version(),1,1)=5) THEN '.*' ELSE '*' END))
SQL sorgusunda; subquery, regex ve boolean condition 'ı birlikte kullanıyoruz. Eğer MySQL sürümü 5.x ise dönen değer "1" olacaktır aksi takdirde #1139 - Got error 'repetition-operator operand invalid' from regexp gibi bir hata alacaksınız.
Zap proxy ile "user-agent" bilgisini şu şekilde değiştirelim :
Veriyi sunucuya ilettikten sonra phpmyadmin ile sonuca bakıyoruz.
"bio" kolonunda 1 rakamını görüyoruz. Demekki veritabanı sunucusu 5.x sürümünü kullanıyor.
Durum vahim, mevcut yazılımda bir enjeksiyon noktası tespit etmiş olduk.
Peki nasıl korunabiliriz?
- Güvenli bir sorgu oluşturmak istiyorsak PDO'i Prepared Statements (Hazır Deyimler) ile birlikte kullanmamız gerekmektedir. Ufak bir örnek yapalım:
$q = $db->prepare('INSERT INTO kullanici(ad, soyad, yas) VALUES (:adi, :soyadi, :yasi)');
$q->bindParam(':adi',$ad);
$q->bindParam(':soyadi',$soyad);
$q->bindParam(':yasi',$yas);
Değiştirgeleri veya soru imlerini PHP değişkenleri ile ilişkilendirmek için PDOStatement::bindParam() yöntemini kullanın; değişkenleri, değerleriyle girdi olarak ve çıktı değerini almak için kullanabilirsiniz.
Kaynak : http://www.php.net/manual/tr/pdostatement.execute.php
EOF



Tırnak arasına koysak (ayrıca özel karakterler escapleniyor mu burada mysql_real_escape fonksiyonundaki gibi ?):
YanıtlaSil$query = "INSERT INTO `authors`(`name`, `bio`) VALUES ( 'one', '$var' )";
Prepared statementlar tek çift tırnakları sorgusuna almaz ve escape işlemini otomatik olarak yapar.
Silburada bindParam fonksiyonu yerine array kullanarak sorgu içerisine parametreleri gömmek arasında bir fark var mı? bahsettiğim kullanım şu şekilde:
YanıtlaSil$db->prepare("select * from tablo where alan = :alan_degeri");
$db->execute(array(":alan_degeri" => "istenilen değer"));
prepared statement yapmak için tek bir yöntem yok. http://php.net/manual/en/pdostatement.execute.php adresindeki örnekleri inceleyebilirsiniz.
Sil