MDB2 Kullanımı ve SQL Injection

Dikkat: Bu yazının yazılma tarihinin üzerinden en az 60 gün geçmiş. İçerisindeki bilgiler güncelliğini yitirmiş olabilir. Yorumları ve güncellemeleri göz önünde bulundurarak yazıyı takip ediniz.

MDB2 Nedir?

MDB2 ilk başta DB olarak başlayan sonra MDB olan ve en son MDB2 ismine dönüşen PEAR'ın veritabanı yönetim paketlerinden birisidir. Oldukça fazla türde veritabanı sürücüsü (driver) bulunduğu için, projenizde bu kütüphaneyi kullanıp fakrlı veritabanı yapılarını aynı kodda rahatça kullanabilmenizi sağlar. Ayrıca "prepare & execute" gibi yapılarla hem sql sorgularınızı daha anlaşılır yapar, hem de injection gibi saldırılar için veri türü kontrollerini otomatik yapar.

Sonuç olarak güvenlik, daha taşınabilir kod, kolaylık gibi özelliklerinden dolayı tercih edilmesi gereklidir diye düşünüyorum. Genel olarak PHP'nin PDO'suna benzeyen bir yapısı var (özellikler bakımından).

Örneklerle Genel Kullanımı

PEAR'ı sisteminize kurmanız gerekiyor. Ya da PEAR'ı kendi projenize gömerek de kullanabilirsiniz.http://pear.php.net/package/MDB2 adresinden MDB2'yi indirebilirsiniz. PEAR dizininizi include_path'a tanıtırsanız şu şekilde sayfa başında MDB2'yi çalıştırabilirsiniz :
require_once('MDB2.php');

Bağlantı

MDB2'yi bağlamak çok kolay. Bağlantı fonksiyonunda DSN (Data Source Name) denilen bir cümle ile veritabanı türü, kullanıcı, şifresi, sunucu, port, veritabanı adını bir url olarak veriyorsunuz. Yani fonksiyona 100 tane parametre girmek zorunda kalmıyorsunuz.

Aşağıdaki örnekten rahatça anlayacaksınız zaten DSN'nin nasıl birşey olduğunu :
$db = MDB2::connect("mysql://kullanici:[email protected]/veritabani");
if(PEAR::isError($db)){
  die( 'Veritabani baglantisi yapilamadi!<br>' . $db->getMessage() );
}
gördüğünüz gibi bir web sitesi url'si tanımlamak gibi. Daha doğrusu ftp erişimi yapmak gibi.
protokol türü + "://" + kullanıcı + ":" + şifre + "@" + sunucu (+":port") + "/" veritabanı

Yani siz aynı veritabanı yapısını oluşturduğunuz sürece projenizi ileride mysql:// yerine pgsql:// yazarak kolayca çevirebileceksiniz.

Neyse, bağlantı kısmı işin başlangıcı. Asıl sık sık kullanacağımız olan şey sorgu işletmek, sonuçları işlemek vs.

Sorgu işletmek

// sorguyu isletirken query fonksiyonunu kullanıyoruz
$sorgu = $db->query("select * from haberler");
// numRows ile donen sonuc kumesinin boyutunu alabiliriz
if( $sorgu->numRows() > 0 ){

  // fetchAll ile tum sonucları çok boyutlu dizi olarak alabiliriz
  // veya while ile donerek fetchRow() fonksiyonu yardimi ile
  // kayitlari sirayla tek tek aldirabiliriz.
  $sonuclar = $sorgu->fetchAll();

  // sorgu kaynagini kaldiriyoruz. Eger bu islemi yapmazsak
  // sunucu yukunu agirlastirabiliriz
  $sorgu->free();

  // sonuclari normal dizi islemleri ile isleyebiliriz
  foreach($sonuclar as $sonuc){
    print $sonuc[baslik] . '<br />';
  }

}
Örnekte tüm detayları açıkladım :-) Eğer çok boyutlu sonuçlarla çalışıyorsanız fetchAll ile halletmenizi öneririm işlerinizi. Eğer tek boyutlu sonuç bekliyorsanız fetchRow işinizi görecektir.

Injection için güzellikler

En sevdiğim kısmı ise sizi verilerinizin türünü kontrol etme işini üstünüzden alması. Bunun için iki yol var. Birincisi query kullanarak quote yapmak. Yani Türkçesi, yukarıdaki methodu kullanarak sorgu işletmek, ama işletirken de değişkenleri tek tek bir fonksiyondan geçirmek. Örnekle anlatırsam :
// elimizde $ad, $ziyaret_sayisi degiskenleri olsun

$sorgu = $db->query('insert into uyeler (ad, ziyaret) values ('. $db->quote($ad, 'text') .', '. $db->quote($ziyaret_sayisi, 'integer') .')');
görüldüğü gibi sql içinde herhangi bir tırnak kullanmadan doğrudan quote() fonksiyonunu kullandım. ilk parametrede veri ikinci parametrede de veri türünü belirtiyorsunuz. Eğer text ise sql injection önlemlerini alıyor. integer ise zaten sayısallaştırıp kaydediyor.

Ancak yine de sqliniz oldukça karışık görünüyor. Yani baksanıza bir sürü tırnak ile SQL cümlesini oluşturan string'den kaçmak için uğraştık. Ayrıca sürekli $db->quote(...) gibi bir fonksiyon çağırmak, elinizde 20 tane alan varsa sıkıcı bir işe dönüşecektir. Bunun için prepare & execute yapısını kullanacağız.

Prepare & Execute

quote kullanımından biraz daha uzun. Ancak eğer sorgunuzda kullanıcıdan gelebilecek bir veri kullanıyorsanız KESİNLİKLE bu yapıyı kullanmanızı öneririm. Yani sonuçta gözünüzden kaçabiliyor bazen değişkenlerinizi izlemek ve kontrol etmek. Bu kullanıma alışırsanız farkında olmadan vereceğiniz açık sayısı en az inecektir.

Gelelim kullanıma, bunu da örnekleyerek anlatacağım :
// prepare ile ilk parametrede sorgunuzu yazıyorsunuz. Değişken olan her yere tırnak fln koymadan sadece soru işareti koyuyorsunuz. prepare fonksiyonunun ikinci parametresinde de dizi olarak sırasıyla o soru işaretlerinin veri türlerini belirtiyorsunuz.
$sorgu = $db->prepare("select * from uyeler where adi like ?", array('text'));

// execute ile dizi şeklinde soru işaretlerine sırasıyla gelecek verinizi veriyorsunuz.
$sonuc = $sorgu->execute("fatih");

// evreka, $sonuc degiskeni bir sonuc kumesine dönüyor. Sonra klasik işlemleri yapıyorsunuz
if($sonuc->numRows() < 1) die("sonuc bulunamadi");
$uyeler = $sonuc->fetchAll();
// ...
Bu örnekte tek değişken gösterdiğim için pek anlaşılmamış olabilir. Ancak alttaki örnekte prepare & execute yapısının önemini kavrayacaksınız.
// elimizde çok değişken olsun

$sorgu = $db->prepare("insert into uyeler (no, adi, eposta, yorum_sayisi, uyelik_tarihi, aktif, www) values (?, ?, ?, ?, now(), ?, ?)", array('integer', 'text', 'text', 'integer', 'integer', 'text'));
// verilerinizi bir diziye koyuyorsunuz, tabiki soru işaretleri hangi sırada ise o sırada koyuyorsunuz
$veri = array($no, $adi, $eposta, $yorum, $aktif, $www);
// basitçe sorgunuzu işletiyorsunuz
if( !$sorgu->execute($veri) ) die("sql çalışmadı");

// işte olayın güzelliği şurada :
// mesela aynı sqlinizi bir daha işletmek istiyorsunuz.
// bunun için $sorgu olan object değişkenini tekrar tekrar kullanabilirsiniz
$sorgu->execute(array(5, 'başka üye', [email protected]', 10, 1, "http://www.deneme.com"));

// hatta eğer sisteminizi daha efektif hale getirmek isterseniz
// bütün SQL'lerinizi fonksiyonlara çevirin. Mesela :
function uye_kaydet($veri){
  // sql hazirlaniyor
  $sorgu = $GLOBALS[db]->prepare("insert into uyeler (no, adi, eposta, yorum_sayisi, uyelik_tarihi, aktif, www) values (?, ?, ?, ?, now(), ?, ?)", array('integer', 'text', 'text', 'integer', 'integer', 'text'));

  // gelen veri isletiliyor
  if( $sorgu->execute($veri) ) return true;
  else return false;
}
Son kısımda bahsettiğim detayı umarım anlayabilmişsinizdir. Çünkü bu şekilde uygulama geliştirdiğiniz zaman, uygulamanız daha esnek, daha rahat müdahale edilebilir ve daha rahat geliştirilebilir hale gelecektir. Siz üye kayıt sayfasını daha sadeleştirmiş olacak, eğer gerekirse üye veritabanında yapacağınız bir değişiklik için gidip üye kayıt sayfasının içinde HTML, PHP karışık kodun içinde aramak yerine doğrudan fonksiyonunuzdaki sql'i güncelleyerek daha hızlı çalışacaksınız.

Bu noktada iş gruplarına ait fonksiyonları sınıflandırırsanız zaten Nesne Tabanlı Programlama (OOP)'ya adım atmış olacaksınız :-)


Hazırlayan : Mehmet Fatih YILDIZ

Yorumlar

6 Ekim Pazartesi ´08 12:09
inj. olayı ve bağlantı kısmı güzelmiş. Teşekkür ederim. Bu OOP ömrümü yedi...
19 Ekim Pazar ´08 15:56
Üstad bağlantı kurmaya çalışınca:

Fatal error: Class 'MDB2' not found in C:\xampp\htdocs\denemeler\mdb2.php on line 4

hatası veriyor? Sayfa başında require_once ile sayfaya dahil ediyorum, orda sorun çıkmıyor.
php.ini dosyasında include_path = ".;C:\xampp\php\PEAR\" şeklinde tanıtılmıi. Acaba baştaki (.) ve (;) ile ilgili bir sorunmu var? Gerçi onları kaldırıp yeniden denedim ama sonuç değişmedi.
22 Ekim Çarşamba ´08 04:16
include path doğru ise hata vermemesi lazım. Ben pear'ı paket şeklinde kullanmıyorum, dosyaları süzüp
projenin içinde kütüphane adı verdiğim bir dizinde topluyorum. ve o dizini include path'e ekliyorum.

set_include_path( get_include_path() . PATH_SEPARATOR . realpath('kutuphane/PEAR'));

komutu ile de include path'e pear klasörünü ekliyorum ve öyle çalışıyorum. ama set_include_path ve get_include_path fonksiyonları veya ini_set + ini_get fonksiyonlarını kullanarak ayarlıyorum proje başında.

senin sorunun ne olduğunu bilmiyorum. include path'leri php iyi anlamış değil sanırım. sayfanın başında get_include_path'ı print edip pear dizininin olup olmadığını kontrol eder misin?
22 Ekim Çarşamba ´08 14:00
Aynısı yazdı üstad: .;C:\xampp\php\pear\
27 Ekim Pazartesi ´08 01:39
Yok usta ne yaptıysam olmuyor.

Birşey soracağım, sen localserver olarak ne kullanıyorsun? xampp, easyphp gibi bir programmı yoksa apache, mysql, php tek tekmi kuruyorsun?
28 Ekim Salı ´08 12:59
Yok yok yok.. serve programı kullanmak yerine apache, php, mysql kendim kurdum. Gerekli ayarları yaptım (neler yaptığımı daha sonra yazarım şimdi vaktım yok   :-)  ) yine

Fatal error: Class 'MDB2' not found in C:\www\denemeler\mdb2.php on line 5

hatası veriyor. Senin yazdığın

set_include_path( get_include_path() . PATH_SEPARATOR . realpath('kutuphane/PEAR'));

koduda sonuç vermiyor. PEAR bana haram   :-)  
28 Ekim Salı ´08 23:25
Gerekli ayarları yaptım derken; kurulumları yaptıktan sonra httpd.conf ve php.ini deki gerekli ayarları yaptım. Php klasöründeki go-pear.bat dosyasını çalıştırıp sadece enter a basarak pear ı kurdum. MDB2&#35;mysql paketini yükledim. Kontrol ettim, PEAR dizinine atmış MDB2 klasörü ve MDB2.php dosyasını. Sonra include_path'i C:/Program Files/PHP/PEAR şeklinde ayarladım. Ve denemelere geçtim ama sonuç malum.

Galiba bu Fatal error un path ile bir alakası yok. Çünkü require_once  hata vermiyor. MDB2 ile veritabanına bağlanmaya çalışmadan önce, yani MDB2 yi sayfada kullanmadan önce require_once hata vermiyor. Kod sadece şu

<?php require_once("MDB2.php"); ?>

Ama veritabanı ile bağlantı kurmaya çalışınca yukardaki fatal error hatasını alıyorum. Senin yazdığın gibi pear dosyalarını aynı klasörde pear isimli bir klasöre atıp set_include_path ile ayarlıyorum. kontrol ilçin get_include_path print ettiriyorum

.;C:\Program Files\PHP\PEAR;C:\www\denemeler\pear

yazıyor. Yani bir yanlışlık yok. Ama sonuç yine aynı.
 
29 Ekim Çarşamba ´08 00:07
set_include_path i şöyle yaptım:

set_include_path( get_include_path() . PATH_SEPARATOR . realpath('PEAR/MDB2.php'));

şimdi sorun yok, sorunsuz çalışıyor   :-)  ama MDB2 den başka bir paket daha kullanmaya kalkarsam ne olacak?
29 Ekim Çarşamba ´08 01:10
Deli olmak işten değil yaa   :-(  Üç gündür uğraşıyorum olmadı. Yukarda yazdığım gibi yaptım oldu. Şimdi sondaki MDB2.php yi siliyorum oluyor. Hatta set_include_path yapmıyorum yine oluyor. Kafayı sıyırdım gece gece
31 Ekim Cuma ´08 16:14
phpinfo, include path'a ne diyor?
31 Ekim Cuma ´08 22:58
.;C:\Program Files\PHP\PEAR yazıyor. Şu anda sorun yok üstad MDB2 yi kullanabiliyorum. Ama baştaki hatayı ve o hatayı nasıl çözebildiğimi hala anlamadım.
Üye Resmi dunkof
9 Temmuz Cuma ´10 21:15
çok sağol temda böyle bişey arıyodum
Üye Resmi Yunus KOCABAY
29 Kasım Cuma ´13 17:34
Yanımda olsan elini eğilip öpmek isterdim harika bir anlatım hemen uygulamaya geçiyorum!
Yeni Yorum *
İletişim Bilgileri
*
*
E-Posta adresiniz gösterilmeyecektir.
(unut)
Güvenlik Kodu *

Gönderiliyor