Daha önce SQL injection ve MDB2 hakkında birşeyler yazmıştım.

Bu döküman birkaç konuda size yardımcı olacak, pratikleştirecek sorunlar :
  • Çok alan (4-5+) kullanırken sorgu cümlesi oluşturmak zor
  • Gözden kaçırıp alanları kontrol etmek bazen mümkün olmuyor.
  • Yukarıdaki 2 maddeyi yapmak için satırlarca duplicate kod yazmak gerekebiliyor

Kısaca 3-4 adımda kocaman bir sorgu cümlesini oluşturacağız. Genel olarak bu dökümandaki sorun insert ve update türü sorgularda çıkan kargaşayı kolaylaştıracaktır. Zira select sorguları hem çok veri içermiyor hem de özel cümleler olabiliyor.

Bu methodolojide doğal olarak bir otomatizasyon var. Bunun için verinin düzenli olması gerekiyor. Bu noktada genelde bir dizi içinde verilerin tutulduğunu düşünelim.
$data = array(
	'ref_code' => $_GET[ref],
	'phone'    => $_GET[tel],
	'text'     => $_GET[yorum],
	'date'     => date('Y-m-d H:i:s'),
	'ip'       => $_SERVER[REMOTE_ADDR]
);
Verinin anahtarları veritabanındaki alan adları ile aynı olmalı. Zaten bu veri dizisini oluştururken gerekli sql injection kontrolleri yapılıp kolayca kurtulabilinir fakat her değerde bir ton fonksiyon çağırmaya gerek yok. Kısaca :
$data = array_map('mysql_real_escape_string', $data);
kullanımıyla tüm diziye mysql_real_escape_string() uygulayabiliriz.

Sonra zaten cümleyi oluşturmak için genel method olan sprintf ile alanları ve değerleri basacağız. Ancak işin güzelliği burada bu alanlar ve değerler kısımlarını bir sürü döngü ile çözmeyeceğiz.
$sql = sprintf('INSERT INTO comments (%s) VALUES ("%s")',
	implode(", ", array_keys($data)),
	implode('", "', array_values($data))
);

Gördüğünüz gibi array_keys ve array_values ile hızlıca alanlar ve değerleri alıp implode ile aralarına virgül ve tırnak ekleyebiliriz. Burada dikkat edeceğimiz şey implode sadece değerlerin aralarına tırnak ve virgül ekleyecektir. En dışda kalan tırnakları sprintf içinde tanımlayacağız.

Sonuç olarak bu noktada $sql değişkeninde, değerler ve alanlar giydirilmiş, aşağıdaki gibi bir sql cümlesi elde etmiş olacağız.
INSERT INTO comments (ref_code, phone, text, date, ip) VALUES ("google", "1234567", "aısdfyısodaf qewrewrewqr 232fsfdsd", "2009-08-25 16:08:34", "127.0.0.1")
Bu cümleyi de mysql_query($sql) ile çalıştırıp sonucu işlemede bitiyor olay.

Hazırlayan : Mehmet Fatih YILDIZ
6 Ekim Pazartesi ´08   —   13 Yorum

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
PEAR'da MDB2 ile veritabanı işlemleri hakkında daha önce döküman yazmıştım (aynı anda yayınladım gerçi). MDB2 ile veritabanı işlemleri ile ilgili bilgi edinmek içinhttp://www.mfyz.com/?/dokuman/99/pear-da-mdb2-ile-......islemleri/ dökümanını okuyun. Bu dökümanda veri işleme ile ilgili kısımlardan bahsetmeyeceğim.


SQL injection, başımızın belası :-)


SQL injextion saldırılarını çok duyuyoruz. Çok da yeni bir şey değil aslında. Fakat çok büyük sitelerde, devlet siteleri, ticari siteler vs. Dikkatsiz programcılar veya dikkatsiz programlama sonucu bu saldırılara kurban gidiyor. Her gün bir yerlerin "hack"lendiğini görüyoruz. Muazzam işler yapmaya gerek yok bu saldırıları yapmak için.

Aynı şekilde bu açıkları vermemek için de muazzam önlemler almanıza gerek yok aslında. Bütün olay kullanıcın girdiği metinleri işlerken ve ekrana basarken kontrollü basmakta yatıyor bu injection, xss saldırılarının altyapıları. Sitenizde sql'inize veya htmlinize enjekte etmek istedikleri şeyleri aslında sizin tırnaklarla sınırladığınız alandan kaçarak başarıyorlar. Tabiki herşey tırnakta çözülmüyor fakat dananın kuyruğunu kopardıkları yer sizin sınırlandırdığınız noktadan kaçabilmekte :-) Yani Hex kodları ile tırnak veya özel karakterler girilmeye çalışıyor, tırnaklar kullanılıyor veya ascii, htmlentity, htmlencode ile özel karakter girmeye çalışılıyor. XSS konusunda burada bahsetmeyeceğim.

SQL injection için eğer pear ve mdb2 kullanmıyorsanız, kullanıcıdan gelen string türü verileriniz için mysql'de mysql_real_escape fonksiyonunu kullanabilirsiniz. Metinlerinizi bu fonksiyondan geçirip sqlinize sokun. Eğer MDB2 kullanıyorsanız Preapre ve Execute fonksiyon grubu ile uygulatmak istediğiniz sql cümlenizdeki değişkenleri işletmeden önce veri türüne göre güvenli hale getirebilirsiniz.

Aslında tek görevi güvenlik değil bu fonksiyonların. Asıl amaç veritabanına doğru türde veri göndermek. Aynı zamanda tek sql'i tekrar tekrar yazmadan birden fazla çalıştırmanızı da sağlıyor. Güzelliklerine az sonra değineceğim :-)

MDB2'de bir sorguyu işletmek için
$sorgu = $db->query("insert into tablom (no, yazi) values (34, 'Metin degeri')");
query fonksiyonunu kullanıyorduk. Diyelim ki 15 tane alanınız var ve hepsini "insert" sql cümlenizde kullanıyorsunuz. query fonksiyonu ile bir sürü tırnak ve değişkeni cümleye dönüştürmek için karışık bir kod yazacaksınız. tek satır görünümünde ama 4-5 satır uzunluğunda birşey olacak :-)

Prepare ve execute fonksiyonlarını aşağıdaki örnekle açıklayayım.
$sorgu = $db->prepare("insert into tablom (no, yazi) values (?, ?)", array('integer', 'text') );
$sonuc = $sorgu->execute(array( 34, 'Yazı yazı yazı' ));
gördüğünüz gibi 2 satırda hallettik işimizi.

$sorgu değişkenine bir sql hazırlıyoruz. Yapılan şey cümle olarak sql cümlesini sunmak, değerlerimizin yerine tırnak fln koymadan doğrudan soru işareti (?) koyuyoruz. prepare fonksiyonuna ikinci paramere olarak, dizi şeklinde veri türlerini veriyoruz. Veri türlerini string olarak elle yazıyoruz ve sorguda hangi sırada ne tür değişken olduğunu açıklıyor bu kısım aslında. Sonuçta $sorgu değişkeninde bir sql cümlesi duruyor, hangi alanlara hangi TÜR veri girileceği belirlenmiş şekilde. Amaçda bu zaten, yani alanlarımıza istemediğimiz türde veri sokmamak. Veri türleri hakkında detaylı bilgi içinhttp://pear.php.net/manual/en/package.database.mdb2.datatypes.php adresini ziyaret edin.

İkinci satırdaysa hazırlanmış sql'i işletiyoruz. Tabiki dizi olarak VERİ'lerimizi veriyoruz execute fonksiyonuna. execute fonksiyonu da $sorgu nesnesinin bir özelliği tabiki. Yani prepare olmamış bir sql execute edilemez. Hata verir. Hatta böyle bir fonksiyon yok der :-)

İşin güzelliğini örnekle açıklayayım :
$sorgu = $db->prepare("insert into tablom (no, yazi) values (?, ?)", array('integer', 'text') );
// birden fazla veriyi ayni sql ile ekleyelim
$sonuc = $sorgu->execute(array( 34, 'Yazı yazı yazı' ));
$sonuc = $sorgu->execute(array( 35, '35. yazı yazı' ));
$sonuc = $sorgu->execute(array( 36, 'Başka yazı yazı' ));
$sonuc = $sorgu->execute(array( 37, 'Yok yok başka yazı bu yazı' ));
söze gerek var mı?

text tipi alanlara otomatik olarak sql injection için kaçmak amacıyla kullanılan özel karakterler dönüştürülüyor. Zaten text dışındaki alanlara herhangi bir string veri kaydedilemiyor. Yani herşey kontrol altında.

Eğer sql cümlenize katacağınız HERHANGİ bir değişken kullanıcı kontrolüyle geliyorsa prepare & execute yöntemini kullanın. Eğer uygulamanızda basit bir sql işlemi kullanıyorsanız işinizi query ile halledebilirsiniz.

Konu hakkında mdb2 manual'da ki bilgiye de göz atmanızı öneririmhttp://pear.php.net/manual/en/package.database.mdb......xecute.php


Hazırlayan : Mehmet Fatih YILDIZ

Popüler Etiketler

ide editor php phpstorm jetbrains deployment version control svn git html css less twitter bootstrap css3 html5 rss lifestream parse xml mssql imza signature apple mail chart api google grafik spam gimp howto jquery javascript plugin optimization media screen örnek kod windows htaccess apache route router mod_rewrite web app open source firefox download opengraph graph facebook share social konsol terminal blog meta oyun iOS pear mdb2 prepare execute mysql pgsql ingilizce mfyz mobile browsers table istatistik sosyal medya compile compiler macosx linux app on-the-fly seo search sql export tool js portfolio nedir cache fstab ntfs ie browser language development style wanda proje kampanya insanlar form input textarea select job sitemap generator url db database service free date tarih license fql kontrol yapıları if while ui ux icons ios itunes banner internet internet explorer kurulum nasıl subversion button optimizasyon diff coding ipad startups cookie subdomain icon ikon calendar logo notebook laptop lisans support statistics mootools object iphone integration network design osx player analyse procedure webkit music ajax xmlhttprequest developer assets http server kitap app store store in-app purchase purchase verification storekit itunes connect session cms newsletter wireless www redirect crossdomain connect box query widget radio switch subscription wordpress ipucu regex login ruffles doritos tytz ubuntu code pharma hack tebrik digital auth framework injection link workspace dokuman applications xhtml zaman