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:
$query = mysql_query("select * from users where id=".$_GET['id']);
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
http://www.huysuzadam.com/kullanici_detay.php?id=5
Peki ben uyanıklık yapıp adres satırını şöyle değiştirirsem:
http://www.huysuzadam.com/kullanici_detay.php?id=1 or 1=1
Bu durumda sql cümlesi şöyle birşeye dönüşmüş olur:
SELECT * FROM users WHERE id=1 OR 1=1
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:
$username = $_POST['username']; $passwd = $_POST['passwd']; $query = mysql_query("select * from users where username='$username' and passwd='$passwd' ");
Bu sefer bir hacker olarak $_POST['username'] değişkenini yine uyanıklık yaparak aynen şöyle gönderiyorum:
a' or 1=1 or 'b
yerine koyduğumuzda sql’imiz şu hale gelmiş oluyor:
SELECT * FROM users WHERE username='a' OR 1=1 OR 'b' AND passwd='asdf'
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:
SELECT * FROM users WHERE username='a\' or 1=1 or \'b' AND passwd='asdf'
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;
$query = mysql_query("select * from users where id=".$_GET['id']);
yerine
$query = mysql_query("select * from users where id='".$_GET['id']."'");
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.
$_POST['username'] = mysql_real_escape_string($_POST['username']); $_POST['passwd'] = mysql_real_escape_string($_POST['passwd']);
şeklinde kullanılır.
Avantajları:
- Ekstra bir kütüphaneye falan gerek yok, doğrudan kullanılabilir.
Dezavantajları:
- Yazması çok sıkıcı
- 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:
require_once '/var/lib/ZendFramework/Zend/Db.php'; $db = Zend_Db::factory('Mysqli', array( 'username' => 'myuser', 'password' => 'mypass', 'dbname' => 'mydbname', 'driver_options' => array(MYSQLI_INIT_COMMAND => 'SET NAMES UTF8;') )); $user = $db->fetchRow("select * from users where id=?", $_GET['id']);
Avantajları:
- Soru işaretinin yerine değişkenimiz güvenli bir şekilde yerleştirildi.
- “fetchRow” metodu sayesinde sadece 1 satır çekilip $user’a eşitlendi. Dolayısıyla mysql_fetch_row vs. yazmanıza da gerek kalmadı.
- MySQL sorguyu cache’leyebildi. Direkt değişkeni yazdığınızda veritabanı onu cache’leyemez.
Dezavantajı:
- Yeni birşey öğrenmek ve uygulamak gerekiyor. Bu da aslında dezavantaj değil avantaj aslında ama anlayana ;)
'96 dan beri web teknolojileri üzerine çalışır.
Ergün
2 Jun, 2011
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.
chveneburo
3 Sep, 2011
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
M.Ozan Hazer
4 Sep, 2011
Verdiğiniz örneğin getParam ile ne alakası var anlamadım ama amaç parametrik olarak userID’yi göndermekse quoteIdentifier() metodunu kullanabilirsiniz.
ömer
12 Sep, 2011
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..