Öncelikle şunu belirtmek isterim ki bir şey ne kadar güvenli olursa olsun tamamen güvenli değildir. Ancak yeterince güvenli olabilir. Yani, aşılamayacak güvenlik önlemi yoktur. O yüzden yaptığınız işe bakıp “oh be ne iş çıkardım içeri sittin sene giremezler.” demeyin. Bir yazılım asla bitmiş değildir ve bir yazılım asla tam koruma altında değildir. Gelişen yazılım dünyasının güvenlik kısmını da takip edip sürekli bir güncelleme içinde olmak gerekiyor. Bugün güvenli olan bir kod öbeği yarın bir açık tehdit olabiliyor.
Bu yazı oturum açma - giriş - login script'i yazarken hangi noktalara dikkat etmeniz gerektiğini anlatır, size hazır bir script sunmaz.
İlk olarak "son kullanıcılara hiç bir zaman güvenmeyin" diye kulağınıza küpe bir cümle ile giriş yapmış olalım.
Kullanıcı girişlerine (input) güvenmek yapılacak hataların başını çeker. Kullanıcıların doğru kutucuğa doğru formatta bilgi gireceğini hiç bir zaman düşünmeyin. Söz gelimi kullanıcı adı bölümünde kullanıcıdan girmesini beklediğiniz bilgi “hasanhuseyin” , “aliveli4950″ gibi bir şeydir. Ancak kullanıcının bu bölüme ‘ or ” = ‘ girmeyeceğini (Bknz: SQL injection) kim garanti edebilir? Bu yüzden hardcoded olarak değerini atamadığınız ve bir sorguda kullanacağınız her değişkeni stripslashes(), addslashes() gibi fonksiyonlarla işleyiniz.
Kısaca Sql inject
Bir login scripti yazarken nihayi olarak yazacağınız kod ve sql kodu şunun gibi olacaktır:
<?php $kullanici_adi = $_POST['kullanici']; $parola = $_POST['parola']; $sonuc = @mysql_query("SELECT * FROM kullanicilar WHERE kullanici = ‘" . $kullanici_adi . "‘ AND parola = ‘" . $parola . "‘ LIMIT 1"); if (@mysql_num_rows($sonuc) > 0) echo 'Sistemime hoş geldin'; else echo 'Hatalı kullanıcı adı ya da parola'; ?>
Eğer bu kodu bu şekliyle yazarsanız. Çok ağlarsınız. Yukarıda da bahsettiğim gibi eğer kullanıcı adı bölümüne sizin beklediğiniz gibi aliveli4950 yazılmaz da ‘ OR ” = ‘ yazılırsa veri tabanına göndereceğiniz sorgu şu hale gelir:
SELECT * FROM kullanicilar WHERE kullanici = ‘‘ OR ” = ‘‘ AND parola = ‘kullanicinin girdiği herhangi birşey’ LIMIT 1
Bu sorgu veri tabanındaki ilk kayda parola bölümünde ne yazarsa yazsın girer. Aynı model kullanıcı adının bilindiği parolanın bilinmediği durumlarda da işe yarar. Bu yüzden SQL sorgusuna göndereceğiniz tüm kullanıcı kaynaklı verileri önce addslashes() fonksiyonu (Regular-Expressions ile de mümkün) ile işleyin.
Aynı injection modeli çerezlerde (cookie) ve get metoduyla aldığınız verilerde de geçerlidir es geçmeyin.
Örneğin şöyle bir bağlantınız var:
./kullanici-sil.php?id=17
Hiç kontrol yapmadan bu id değişkenini sql sorgusuna gönderirseniz. Gene ağlayabilirsiniz. Adresin sonuna kullanıcının şu satırcıkları eklediğini düşünelim
./kullanici-sil.php?id=17‘ OR 0 = 0 #
Sıfır her zaman sıfıra eşittir. Bir de bire. İki ise ikiye… Yani her zaman geçerli olacak ufacık bir kod yumağı sizi ne hale getirir düşünün. (Örnekte yetkilendirme kontrolü verilmemiştir, yapın tabi kullanıcı silerken bakın bakalım bunu isteyen kullanıcının bu işleme yetkisi var mı? Öyle her önüne gelen kullanıcı silse.. ohoooo. )
id gibi sadece rakamsal olmasını beklediğiniz değişkenleri is_integer() fonksiyoncuğuyla kontrol etmeniz yeterlidir.
Sessionlar güvenlik açısından sağlamdır. Ancak eğer paylaşılan bir sunucu kullanıyorsanız hangi session u hangi ip adresi için açtığınızı veri tabanında tutun. Paylaşılan sunucularda php temp dizini aynı yerde olacağı için o sunucudaki herhangi bir site sizin siteniz için geçerli bir session oluşturabilir. Bunu engellemenin yegâne yolu da hangi session u (session_id()) hangi ip adresine açtığınızı bir tabloda tutmaktır.
Veri tabanında parolaları tutmak için char(40) alan oluşturun. Oluşturduğunuz bu alana kayıt yaparken ya da parola kontrolü yaparken sha1() fonksiyonunu kullanın.
Örneğin
$pasword = '123456'; echo sha1($password); // 10470c3b4b1fed12c3baac014be15fac67c6e815 gibi bir çıktısı olacaktır.
Bunu ürettiğiniz her hata mesajı için yapın. Örneğin bir giriş formunda “kullanıcı adınız yanlış” ya da “parolanız sondaki 6 rakamı eksik” gibi mesajlar vermeyin. Sadece “geçersiz giriş denemesi” ya da “geçersiz kullanıcı adı ve/veya parola” deyin.
Php de birçok fonksiyon hata ya da uyarı mesajları gösterir. Bu uyarı ya da hata mesajlarının ekranda gözükmesi geliştirme süresince sizin yardımcınız olsa da, son kullanıcıya hitap eden bir sistemde yalnızca saldırganlara hizmet eder.
Örneğin:
Warning: file_exists() [function.file-exists]: open_basedir restriction in effect. File(/usr/local/lib/php/extensions/no-debug-non-zts-20060613/../../../../../../home/ioncube/ioncube_loader_lin_5.2.so) is not within the allowed path(s): (/home/rxsex:/usr/lib/php:/usr/local/lib/php:/tmp) in /home/rxsex/public_html/fonksiyon.php on line 2
Tarzı bir hata mesajı hangi dosyanızın hangi satırında hangi işlemleri yapmaya çalıştığınızla ilgili bilgi içeriyor. Bu hata mesajlarını fonksiyonların başlarına @ işareti getirerek susturabilirsiniz. Daha sonra da işlemin başarıyla gerçekleşip gerçekleşmediğini sonuç değişkenlerinden (her fonksiyon döndürür) ya da mysql_affected_rows() gibi fonksiyonlarında yardımıyla öğrenip hata yada başarı mesajlarınızı kullanıcıya gösterin.
HTTP_REFERER istemini bulunduğunuz sayfa kullanıcının buraya hangi adresten geldiğini döndürür. Ancak aldatılması kolaydır. Sadece bir kaç çaylağı ve basit spam botlarını engeller. Çok güvenmeyin ama yine de kullanın.
Örneğin kullanıcı adı kutucuğunuz en fazla 32 karakterden oluşacaksa önce input kutusunun “maxlength” özelliğini kullanın sonra da substr() gibi fonksiyonlar yardımıyla kırpın.
Örnek
<input type="text" maxlength="32" name="kullaniciadi"> <?php $kullaniciadi = substr($kullaniciadi,0,32); ?>
Sayfaya post metoduyla gönderdiğiniz bilgileri $_POST ile alın $_REQUEST ile değil. $_REQUEST get metoduyla gönderilmiş formlarında bilgilerini alır.
Tek sonuç döndürmesini / işlemesini beklediğiniz her sorgunun sonuna LIMIT 1 özelliğini ekleyin.
Birçok kez login denemesinde başarısız olan ip adresini geçici bir süreliğine engelleyin. (15 dk?) Yada yine geçici bir süreliğine resim kontrollü (captcha) giriş ekranına yönlendirin.
Kritik hataların ve giriş denemelerinin loglarını tutun.
Örneğin
$result = @mysql_query($query) or die("Öldü bu, olmadı bu sorgu");
Yerine
$result = @mysql_query($query) or logla_baboli("Hata dosyası: ABCD.php, satır: 56", mysql_error(), $kullanici, time());
daha sonra da $result değişkenine göre sayfada hata veya başarı mesajı çıkartın.
Global değişkenleri register etme php.ini dosyasında aktif edildiyse (register_globals = On) sayfa global değişkenleri GET metoduyla atanabilir olur. Deneyin:
<?php if ($kacki_bu_sayi > 0 ) echo "Yok artık..."; ?>
bunu yerel sunucumuzda test dizinin altına test.php olarak kaydettik ve şu adrese girdik diyelim
http://localhost/test/test.php
adres bu şekilde yazdığımızda hiç bir sorun yok. Sayfa beyaz ötesi ace kıvamında. Ancak şu şekilde yazarsak:
http://localhost/test/test.php?kacki_bu_sayi=2
o zaman sayfa ebemizin kulaklarını çınlatıyor.
Bunu engellemenin 2 güzide yolu mevcut.
1: php.ini dosyasında register_globals özelliğini kapatın (register_globals = off)
2: Eğer php.ini dosyasına erişiminiz yoksa her değişkenin işlemden önceki değerini atayın.
Örnek:
<?php $kullanici_giris_yaptimi = false; //if (giriş kontrolü kodu) $kullanici_giris_yaptimi = true; ?>
Son olarak SSL
Daha fazla güvenlik için giriş scriptlerinizde SSL kullanın.
4 yorum
Helal olsun!Elinize sağlık...
Yanıtla
güzel faydalı bir yazı olmuş teşşekrler
Yanıtla
Çok teşekkür ederim. Çok yararlı bir yazı olmuş...
Yanıtla
Güzel yazı olmuş teşekkürler.
Yanıtla