PHP’de güvenlik, genel bir bakış
Öncelikle konu, bir yazıda incelenemeyecek kadar geniş olduğundan ancak genel bir bakış diyebiliyorum. İkincisi, aslolarak bu mesaj POT (php.org.tr) ‘de, forumda açılan bir konu için yazıldı. Fakat ironiye bakın ki, forumun kötü yazılmış güvenlik rutinleri nedeniyle kabul edilmedi!
Neyse yazının devamında basit kurtarıcı bir iki konfigurasyonlarından, sql injection, lfi, rfi, xss gibi genel web saldırılarından kurtarmanın yollarına dair bir yazı bulacaksınız.
PHP Konfigurasyon (php.ini) hataları:
register_globals: Mutlaka kapalı tutulmalıdır, performansı sömüren ve programcının php betiğinin dışında değişken oluşturulmasının doğal olarak pek hesaba katılmadan yazılan betiklerde ciddi güvenlik sorunu oluşturabilir. Ayrıca bu seçenek POST, GET, SESSION ve COOKIE de aynı değişken adlarının da kullanılamamasına sebep olacaktır. Örneğin $_SESSION['id'] ve $_POST['id'] birlikte kullanıldığında $id değişkeni, her zaman $_POST['id'] içeriğini alacaktır. PHP 6.0′da bu özellik tamamen kaldırılacaktır. Şimdiden kapatmaya alışın.
Örneğin, çeşitli kontroller yapıldıktan sonra yetki kontrolü sağlayacak bir bit degiskeninin kullanılması, register_globals açık oldugunda ciddi bir sorun olusturur:
< ?php
if (checkAdmin($_POST['kullanci'],$_POST['sifre'])) $admin = 1;
if ($admin = 1) {
....
}
bu betikte register_globals açıksa, kolaylıkla http://localhost/yetki.php?admin=1 ile yetki alınabilir.
register_globals dışında, böyle durumları egale etmek için variable type ‘da $admin === 1 şeklinde kontrol edilebilir, fakat doğru olan register_globals’in kapatılmasıdır.
gpc magic quotes: Bir başka performans katili olan magic_gpc, kullanıcıdan gelen veriyi sql injection, mail header injection, shell injection ‘a karşı “, ‘ ve \ karakterlerini otomatik escape eden bu özellik, malesef bu ataklara karşı korunma konusunda yeterli değildir. HEX SQL Injectionlara karşı hiçbir yetkinliği olmamasının yanı sıra, shell ve mail headerlara karşı korunmada da çok başarısızdır. Aynı zamanda sizin kendi elinizle yaptığınız escapeleri de çiftleyeceğini unutmayınız. PHP 6.0′da bu özellik tamamen kaldırılmıştır. Şimdiden kapatmaya alışın.
< ?php
function guvenliDegiskenler(){
if(!empty($_POST) && is_array($_POST)){
foreach($_POST as $deger){
if($deger <> ”){
$deger = mysql_real_escape_string(addslashes(trim($deger)));
}
}
}
if(!empty($_GET) && is_array($_GET)){
foreach($_GET as $deger){
if($deger <> ”){
$deger = mysql_real_escape_string(addslashes(trim($deger)));
}
}
}
}
Hem addslashes, hem real_escape_string kullanılırsa karakterlerin bazilari (’ , \ ) iki kere escape edilir. Bu stringleri db’den cekip her kullanisimizda bir de stripslashes ile bogusmamiza neden olur. Eger hele bir de magic quotes açık ise, mysql’e girilmiş verinin iki kere stripslashes yemesi gerekir.
Yukarıdakini bu olumsuzluklara karşı genel bir rutin işlemi gibi yapabilirz;
yani,
< ?php
if(!empty($_POST) && is_array($_POST)){
foreach($_POST as $key=>$value){
//magic quotes açıksa, önce stripslashes çünkü biliyoruz ki magic quotes sql injectiona karşı işinin ehli değil.
if(get_magic_quotes_gpc()) $value = stripslashes($value);
$$key = mysql_real_escape_string(addslashes(trim($value)));
}
}
if(!empty($_GET) && is_array($_GET)){
foreach($_GET as $key=>$value){
if(get_magic_quotes_gpc()) $value = stripslashes($value); //magic quotes’u yoket.
$$key = mysql_real_escape_string(trim($value));
}
}
Fakat bu sefer,register globalsin görevini üstlenir, dolayısıyla hem açık bırakıldığındaki gibi bir güvenlik sorunu oluşur, hem de performansdan kaybederiz. Çünkü saldırgan, GET/POST parametresiyle, istedigi isimde php variable’ı oluşturabilir.
Not: Yukarıdaki betik, sizin bir sql injection güvenlik sorunuyla karşı karşıya kaldığınızda fakat nereden kaynaklandığını bilmediğiniz bir durumda, genel include dosyanızın başına koyarak geçici de olsa çözebileceğiniz bir durumdur.
Eğer XSS yani cross site scripting açığınız varsa, ( bunu en iyi oturumlarınızın çalınmasından anlayabilirsiniz, başlıca denenen budur ) mysql-real_escape_stringin yanı sıra striptags(); da kullanabliirsiniz.
Önemli not: mysql_real_escape_string, mysql_escape_string’den farklı olarak, escape edilecek karakterleri mysql’in bizzat kendisinden öğrendiği için mutlaka mysql bağlantısı yapıldıktan sonra kullanılmalıdır. Eğer PHP versionunuz 4.3.0 ‘dan eskiyse, mysql_escape_string’i kullanabilirsiniz.
Geçerli yöntem ve kalıcı çözüm, herşeyi halleden sihirli fonksiyonlar:
Güvenlik bakım işidir ve kalıcılığı yoktur. Sihirli fonksiyonlar ise, belki PHP’nin peri dünyasında varlar, onlar sadece bizi rüyalarımızda ziyaret ederler.. Evet, çok güzel yazdınız fakat egonuza yenik düşmeyin. Her ne kadar ince eleyip sık dokusanız, kodunuza şöyle bir baktığınızda çok sağlam ve açık kapısı yok gibi gözükse de, İlerde bir gün bir şey çıkar ve butün ana işlevlerinizi gözden geçirmeniz gerekebilir. Bakımı kolaylaştırmak ise, sadece bir uygulama kurgusu kullanılarak sağlanabilir. Kurgu MVC gibi komplike bir yapı da, çok basit bir şekilde, ara transparan fonksiyonlar üretmek olabilir. MVC yapılarını Zend Framework, cakePHP, Code Igniter gibi frameworklreden bakabilirsiniz, ben sadece çok küçük bir mysql_wraper göstereceğim.
SQL Injection: Sadece veritabanı işlemlerinde gerçekleştiğinden, sql wraperınızı ilgilendirir.
Örnek wraper:
< ?php
function mysql_wrap_query($str,$arg) {
$str = str_replace("?","%s",$str);
foreach($arg as $k=>$v) $arg[$k] = mysql_escape_string($v);
array_unshift($arg,$str);
$str = call_user_func_array(”sprintf”,$arg);
$res = mysql_query($str);
return $res;
}
Kullanım:
< ?php
mysql_wrap_query("SELECT * FROM tb_user WHERE user='?' AND pass='?'",array($_POST['user'],$_POST['pass']));
Böylece her yerde, mysql_query yerine mysql_wrap_query kullanırsanız, tek yerden butün injection problemlerinizi çözme imkanınız olur. Bunun yerine, kullanımından esinlendiğim ve çok daha geniş içeriklikli iyi tasarlanmış PHP PDO kullanmanızı öneririm.
RFI , LFI saldırıları, bir dosyanın include veya upload yoluyla karşıdan çalıştırılmasına olanak verir. Dolayısıyla upload veya load wrapperınızı ilgilendirir.
RFI (remote file include) iki şekilde gerçekleşir, kullanıcının sunucuya çalıştırılacak kod upload etmesi veya PHP’nin çalıştırılacak kodu, başka bir sunucudan çekmesinin sağlanması.
1, Güvenli Include: bu saldırı, include include_once, require ve require_once ‘u ilgilendirir. include ve require kullanılması, iki kere include edilecek dosyalarda duplicate yaratacağından önerilmez. include_once ve require_once da kullanım farkı bulunmaz, o yüzen aşağıdaki örneği veriyorum.
< ?php
function load($file,$remote=0) {
if (!$remote) $file = str_replace(array(":","//"),null,$file);
require_once($file);
}
Kullanım:
Eğer gerçekten başka bir siteden kod alıp çalıştırmak istemiyorsanız, basitçe include veya require yerine load() kullanarak çağırabilirsiniz. Başka siteden özellikle alıp kod çalıştırmak istiyorsanız ki çok çok istisnai bir durumdur, load(”deneme.php”,1); ile güvenliğin tam olarak sağladığınızdan emin olarak çağırabilirsiniz. Niye o zaman çıplak include kullanmayayım ki demeyin, güvenliği -şimdilik- sağladığınızı unutmayın. Gelecekte n’olur, bilemezsiniz.
2. Güvenli Upload:
Çok genel bir upload sınıfı hazırlayamayacağım. Zira grafikten, videoya çok fazla dosya extensionu (.mpeg,.jpeg,.bmp, ..) var ve MIME check etmekle bitmiyor, çünkü çoğunun düşündüğünün tersine MIME check kandırılabilir. Dolayısıyla elimizde bir tek extension check kalıyor ki saldırganların PHP dosyaları çalıştırılamasın. Öncelikle, bunun en temiz yolunun linux klasör modlarının, upload edilecek dizinde execute hakkının verilmemesi olduğunu söyleyeyim. Dediğim gibi hazır, yüklüce bir sınıf yazmıyorum, nitekim burada ben ne güvenliğin, ne de PHP’nin öğreticiliğini yapıyorum. Bu konuda Phpclasses’da iyi upload classları var, fakat sizin ihtiyaçlarınıza tam olarak eğilip bükülebilecek bir yardımcıyı ancak zend framework gibi büyük frameworklerde bulabilirsiniz. Yoksa kendiniz yazmalısınız.
Takılırsanız yardımcı olacak bir kaç şey;
file extension almak için,
< ?php
$a = explode(".",$filename);
$extension = array_shift($a);
upload edilecek dizin eğer değişkenliyse, bu değişkeni, “..” gibi dizin yönlendirme stringlerinden arındırmanız.
Örnek:
< ?php
$hede = str_replace(array("..","//","",$hede)
$dir = "//var/www/$hede",
move_upload_file($file,$dir);
Upload yapılan dosyayı 444 (read,read,red) hakkı vermeniz. Dolayısıyla diyelim ki bir şekilde güvenlikten geçerse web sunucusu tarafından çalıştırılamasın. İstenmeyen bir kod çalıştırılırsa, sizin çalışan dosyalarınızın içine sızmadan, veritabanınıza kadar ne kadar büyük bir zarara uğrayabileceğinizi unutmayın, önlem her türlü iyidir. Bu seçenek sadece unix/linux/bsd OS’larda var.
XSS Injection: Javascript veya HTML kodu ile tasarımınızı bozmaktan, kullanıcı oturumlarınızı (yönetici oturumları dahil) çalınmasına olanak veren ve oldukça yaygın bu güvenlik sorunu Template / Output rendererınızı ilgilendirir.
Bunun için oturup kullanışlı bir renderer yazmak fazla kaçar ve php kodu eğer HTML kodu ile spagetti yapılmışsa kolay kolay uygulanamaz (html içine < ??> kodlayarak yazılan uygulamaları kastediyorum). Size önerim, HTML ile PHP’yi hem bakım kolaylığı hem de kodun anlaşılabilirliğini yükseltmek için bir an önce birbirinden smarty, flexy gibi template sistemleriyle ayırın. Bu sistemleri iyice incelediğinizde, içlerinde XSS’i önleyen escape gibi pek çok iş kolaylaştırıcı modül olduğunu göreceksiniz.
Shell Injection: system(), exec(), passthru() gibi php’nin shellde komut çalıştırmasına olanak veren bu fonksiyonların, escape’inin sağlanması önceliklidir. Çok nadiren kullanılan bu işlevlerde, tıpkı sql injectiondaki komutu kullanabilirsiniz. Tek fark, mysql_escape_string yerine escapeshellarg ve, mysql_query yerine exec kullanmaktır.

Ağustos 19th, 2008 @ 12:58 am
Guzel yazı keske usenip Acıkların tamamını anlatsaydın.
Tessekurler.
Ağustos 19th, 2008 @ 1:11 am
Biri bunu diyecekti.. Biraz daha genişlettim, fakat konuların ayrı ayrı ele alınması gerektiğini ve uygulamalarda kurgunun yapılandırılmasının başlı başına bir iş olduğunu söylemeliyim. Kurgu için fikir vermesi açısından ‘MVC frameworksüz MVC’ adlı bir yazı yazıyorum, fakat başlıkların her biri için teker teker derinine inmeyi başka yarınlarda yapabilirim.
Buarada var olan MVC frameworklere genel bakış yazımdan da faydanalabiliyorsanız ne ala: http://www.gokceyalcin.com/framework_start
Ağustos 19th, 2008 @ 9:51 am
MVC güzeldir.. Framework’lere ısınamadım hala :)
Ağustos 19th, 2008 @ 12:26 pm
özellikle gpc magic quotes işime yaradı. Diğerleri de çok iyi yazı, Birazda şu shell açıktan bahsetsek süper olurdu bence
Ağustos 19th, 2008 @ 4:52 pm
“MVC Frameworksüz MVC” isimli bir makalesi var PHP kurucu yaratıcılarından Rasmus amcanın. Onun başladığı şeyi belki routerıydı, PDO bağlantısıydı bikaç adım ileri götürüp bir sonraki çeviri / uygulamalı anlatımım o olacak. İlgini çeker o zaman ;)
Eylül 4th, 2008 @ 10:44 pm
güzel bir yazı olmuş teşekkürler.
güvenli include için vermiş olduğunuz kod, anladığım kadarıyla “local file include” saldırılarını engellemiyor.
local file include saldırılarının güvenli konfigure edilmemiş sunucularda uzaktan kod çalıştırmaya kadar gidebildiğini düşünürsek, include işleminin bir beyaz liste ile yapılması gerektiğini düşünüyorum.
bu beyaz liste el ile veya /inc klasörü gibi bir klasörden otomatik olarak oluşturulmuş array ile oluşturulabilir. daha sonra gelen isteğe göre kontrol edilir.
sevgiler.
Eylül 9th, 2008 @ 5:31 am
bu web programlada güvenlik konusu çok ilginç. insanı inanılmaz paronayak yapıyor. dengesini bozuyor. ne zaman güvenlik konusunda birşeyler okusam acaba mevcut işlerimde gözümden kaçan birşey varmı? vs diye kodlarımı gözden geçirip dururken buluyorum kendimi. :)