SQL Injection nedir, nasıl önlenir?

PHP’yi ya da web tabanlı herhangi bir yazılımı öğreniyorsanız güvenlikle ilgili ilk öğrenmeniz gerekenlerden biri SQL injection.

Hala uzmansorusu.com‘da sorulan soruların ciddiye alınacak bir kısmında, üniversiteye gelen stajerlerde, hatta çalışma fırsatım olan arkadaşlarda bu konuda bir bilgisizlik var…

Aslında bu blog’a başlarken ileri düzey konulara, framework’lere falan değineyim istemiştim ama pes ediyorum artık. Şuraya yazayım da en azından lazım olduğunda al oku işte diye adresini veririm :)

SQL Injection Nedir?

Türkçeye çevirirsek SQL’in içine zehir enjekte edilmesidir diyebiliriz. Örnek vermek gerekirse elimizde bir kullanici_detay.php dosyası olsun:

satırını ele aldığımızda, $_GET[‘id’]’nin bir sayı olduğunu farz ederiz ve kod düzgün çalışır. Bu sayfanın adresi de şöyle birşeydir

Peki ben uyanıklık yapıp adres satırını şöyle değiştirirsem:

Bu durumda sql cümlesi şöyle birşeye dönüşmüş olur:

1=1 önergesi her zaman doğru döndüreceği için tüm kullanıcıları listeletebilirim bu durumda…

Ne Zararı Olur?

Tüm kullanıcıları görse nee, görmese ne diyebilirsiniz. Peki bu güvenlik açığı ile neler yapılabilir?

Az önceki örneği değiştiriyorum, bu sefer biraz daha karışık işlere girelim:

Bu sefer bir hacker olarak $_POST[‘username’] değişkenini yine uyanıklık yaparak aynen şöyle gönderiyorum:

yerine koyduğumuzda sql’imiz şu hale gelmiş oluyor:

Gördüğünüz gibi OR’lar sayesinden herhangi bir kullanıcı olarak girebiliriz. SQL’in son hali kolay okunsun diye ben böyle girdim ama biraz yaratıcılık kullanılarak daha kısa ya da daha farklı yaramazlıklar yapılabilir.

Nasıl Önlememelisiniz : magic_quotes_gpc

PHP’nin eski versiyonlarında magic quotes gibi bir ayarla SQL injection’a çözüm ürettiğini zannetti Zend firması. Aslında bu daha sonra çok baş ağrısı yaratacak bir karardı ve o yüzden de PHP6’da yanılmıyorsam tamamen kaldırılacak bu özellik.

Magic Quotes şu şekilde çalışır:

php.ini’den magic_quotes_gpc aktif edilirse PHP otomatik olarak tüm $_GET, $_POST ve $_COOKIE değişkenlerinin içindeki tırnak işaretlerini \’ ve \” ile değiştirir. Bu durumda ikinci örneği ele alırsak sql şu hale gelmiş olur:

Bu durumda \ işareti yanındaki karakteri “escape” eder, yani tek tırnak işaretinin tırnağı kapama görevine son verir. Böylece veritabanı hakkaten de adı a\’ or 1=1 or \’b olan bir marslıyı (!) bulmaya çalışır.

Kırma girişimi başarısızlıkla sonuçlanır, dünya kurtulur.

Burada dikkat edilmesi gereken tek nokta, yazının başında verdiğimiz örnekteki gibi sayıları kontrol ederken de tırnak işareti kullanmamız gerekir.

Yani;

yerine

yazmamız gerekir.

Ancak sakın magic_quotes_gpc yönetimini kullanmayın!

Çünkü bu konuda iki büyük sorun var: Birincisi psikolojik olarak buna o kadar alışıyorsunuz ki sanki bu magic_quotes_gpc açıksa bir daha asla SQL injection yemezmişsiniz gibi geliyor. Sonra da biri gelip bu sefer de /* */ kullanarak SQL injection yapıyor. Ya da görünmez ASCII karakterleri ya da diğer muhtelif yöntemler.

(Not: Bu muhtelif yöntemleri burada tek tek yazmayacağım ki bu doküman bir “hacker’ın el kitabı”na dönüşmesin ;) )

İkinci büyük sorun da, hiç başınıza gelmeyecek zannetseniz de hosting değiştirme problemi. magic_quotes_gpc’ye güvenirken, bir anda bu ayarın kapalı duruma geldiğini düşünebiliyor musunuz? Kolay gerçekleşecek bir senaryo değil gibi gözükse de gayet olasıdır, tecrübeyle de sabittir!

3. daha önemsiz sorun ise, “Ozan’ın kalemi” gibi bir cümledeki tırnak işaretini de escape etmesi. Bu durumda veritabanına yazmadığınız durumlarda “strip_slashes” gibi bir işlem yapmanız gerekiyor ki bu da bazen inanılmaz can sıkıcı olabiliyor.

SQL Injection Nasıl Önlenir?

Gelelim sadede. Bunu yapmanın birkaç yöntemi var. Bana en makul gelenleri:

mysql_real_escape_string

Bu fonksiyon, değişkenlerin içindeki tüm zararlı karakterleri “escape” eder, yani güvenli hale getirir.

şeklinde kullanılır.

Avantajları:

  1. Ekstra bir kütüphaneye falan gerek yok, doğrudan kullanılabilir.

Dezavantajları:

  1. Yazması çok sıkıcı
  2. MySQL’den başka veritabanı kullanırsanız ne olacak?

Bind etme yöntemi

Aslında bunu mysql konutları ile yapmak da mümkün, ama zahmetli olduğu için bir database wrapper kullanarak yapılışını anlatacağım.

Bir database wrapper ya da PDO kullanılır. Örneğin Zend_Db kullanalım:

Avantajları:

  1. Soru işaretinin yerine değişkenimiz güvenli bir şekilde yerleştirildi.
  2. “fetchRow” metodu sayesinde sadece 1 satır çekilip $user’a eşitlendi. Dolayısıyla mysql_fetch_row vs. yazmanıza da gerek kalmadı.
  3. MySQL sorguyu cache’leyebildi. Direkt değişkeni yazdığınızda veritabanı onu cache’leyemez.

Dezavantajı:

  1. Yeni birşey öğrenmek ve uygulamak gerekiyor. Bu da aslında dezavantaj değil avantaj aslında ama anlayana ;)

1 Yorum

  1. Makale için teşekkürler, güzel bir yazı olmuş.
    Evet dediğiniz gibi magic_quotes php 5.3 üzerinde önerilmemekte ve 6’dan sonra tamamen kaldırılacaktır.

    Reply
  2. Selam Ozan,

    Güzel yazı olmuş. Özellikle Zend tarafı ilgimi çekti. Zend_DB ile dediklerin OK ancak Zend_Db_Select kullanıyorsan işler biraz değişiyor.

    Geçen gün Zend DB listesinden bu bilgi şans eseri geldi. Bilgini sahibi Korn Ness diye bir arkadaş. Buraya yazıyorum belki birilerinin işine yarar.

    İstem için gelen parametrede yani $getRequest()->getParam da ne kadar escape() ederseniz edin, SQL injection tehditi bulunmakta.

    Örneğin

    $select = $this->select()->from(array(‘dict_users’))
    ->where(‘userID = ?’, $userID);
    dediğin anda SQL satırının çıktısı;

    SELECT dict_users.* FROM dict_users WHERE (userID = ‘1’) oluyor. Burada dikkat edilmesi gereken konu tüm sql satırının ters tırnak ile korunmaya alınmasına rağmen, userID kolonu ters tırnak dışında bu da SQL injection’a olanak veriyor.

    Çözümü için bakınıyorum.. Bilen varsa yazarsa sevinirim..

    Kolay gele

    Reply
  3. Verdiğiniz örneğin getParam ile ne alakası var anlamadım ama amaç parametrik olarak userID’yi göndermekse quoteIdentifier() metodunu kullanabilirsiniz.

    Reply
  4. Merhaba, anlatımınız güzel olmuş. Elinize sağlık. Benim merak ettiğim bir konu var, normal iletişim sayfasına (admin girişi vs olmayan) nasıl drop table yapılabilir?(bir arkadaş sql injection önle yoksa drop table yapıcam dedi kafama takıldı.) 2. sormak istediğim şu baglan.php vs sayfamıza ekleyerek(include..) tüm sayfalara uygulabileceğimiz bir yöntem var mı? yani tüm post, get metodlarını vs kontrol edebileceğimiz..

    Reply
  5. Aslında en büyük sistem açığıdır bir çok üniversite sitesinde hala sql hataları yüzünden hacklenmektedir geçen gün ismini vermeyeceğim bir üniversite sitesinin bütün table’larını okuyup admin hesabı ile bağlanıp panele ulaştım uyarı mail’i atmama rağmen açığı hala kapatmamaışlar malesef umarım sizin yazınızı görüp ders alıp açıklarını kapatmayı öğrenirler

    Reply

Leave a Comment.