23 Ağustos 2019 Cuma

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

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

OWASP (Open Web Application Security Project)’in hazırladığı, OWASP TOP Ten 2010 ve 2013raporları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[3] = ‘t’;
    foo[2] = ‘x’;
    }
    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 ile.
    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): 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
    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
    <?php
    $language = $_GET["language"]; ///etc/passwd
    include("websites/$language/home.php");
    //"websites//../../../../../../../../../etc/passwd/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
    <?php
    $language = $_GET["language"]; ///etc/passwd
    include("websites/$language/home.php");
    // "websites//../../../../../../../../../etc/passwd/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);
    }

0 comentários:

Yorum Gönder