Learn the Capital

Bu yazıda basit görünen bir ülke–başkent uygulamasının, sunucu tarafında yapılan kritik bir hatadan dolayı nasıl File Inclusion (LFI) zafiyetine açık hale geldiği incelenmektedir.

Uygulamanın Genel Yapısı

Uygulama kullanıcıya bir <select> alanı sunar ve seçilen ülkeye göre ilgili PHP dosyasını yükler. Arayüz masum görünmektedir:

  • France → france.php
  • Germany → germany.php
  • Turkey → turkey.php
None

Kritik Nokta: Kaynak Kod İncelemesi

Zafiyetin tamamı aşağıdaki kod bloğunda gizlidir:

if(isset($_GET['country'])){
    $page = $_GET['country'];
    include($page);
}

Bu noktada üç kritik gerçek ortaya çıkar:

🔴 1. Kullanıcı girdisi doğrudan kullanılıyor

$_GET['country'] değeri hiçbir filtreleme veya doğrulama olmadan alınmakta.

🔴 2. Whitelist yok

Sunucu tarafında:

  • izin verilen dosyaların listesi yok
  • france.php, germany.php gibi dosyalarla sınırlama yapılmamış

🔴 3. include() fonksiyonu doğrudan çalıştırılıyor

include() PHP'de dosya yolunu olduğu gibi işler. Bu fonksiyon:

  • aynı dizindeki dosyaları
  • alt dizinleri
  • hatta yetkisiz admin dosyalarını bile yükleyebilir.

Bu üç durum bir araya geldiğinde uygulama tam anlamıyla File Inclusion'a açıktır.

Zafiyetin Tespiti: HTTP İsteği Üzerinden Düşünmek

Form gönderildiğinde sunucuya giden gerçek istek şudur:

GET /index.php?country=france.php

Kaynak koddan şu çıkarım yapılır:

"Sunucu country parametresinin içeriğini sorgulamıyor, sadece include ediyor."

Bu durumda mantıksal soru şudur:

Eğer france.php yerine başka bir PHP dosyası gönderilirse ne olur?

Burp Suite ile Exploitation Mantığı

country=../admin.php

Gönderilen istek:

GET /index.php?country=../admin.php HTTP/1.1
None

Sunucu Neden Bunu Kabul Etti?

Çünkü sunucu tarafında:

  • dosya adı kontrolü yok
  • dizin kısıtlaması yok
  • whitelist yok

Sunucu şu şekilde çalıştı:

  1. $_GET['country'] alındı
  2. Değer admin.php
  3. include(admin.php) çalıştırıldı
  4. Dosya mevcut olduğu için başarıyla yüklendi

Sonuç olarak sayfa içerisinde şu ifade render edildi:

Welcome to the Admin page..

Bu çıktı, yetkisiz bir kullanıcının admin dosyasını doğrudan include edebildiğini kanıtlar.

None

1. İlk Gözlem: Uygulama Nasıl Çalışıyor?

Lab açıldığında karşımıza basit bir web sayfası çıkıyor. Menüde farklı sayfalara tıkladıkça URL'in değiştiğini fark ediyoruz:

index.php?page=home.php

Burada çok önemli bir detay var:

  • page adında bir parametre var
  • Bu parametre bir dosya adı içeriyor (home.php)

Bu bize şunu düşündürmeli:

"Bu uygulama, hangi sayfanın gösterileceğine URL üzerinden karar veriyor."

Yani kullanıcıdan gelen page parametresi sunucu tarafında kullanılıyor.

2. Kaynak Kodunu İncelemek

Bu labda kaynak kodu görebiliyoruz ve asıl her şey burada netleşiyor.

Kodun ilgili kısmı şu mantıkta çalışıyor:

<?php
include($_GET['page']);
?>

Bunu sade bir dille açıklayalım:

  • Kullanıcı URL'e ne yazarsa
  • PHP bunu alıyor
  • Hiç kontrol etmeden include() içine koyuyor
  • Sunucu o dosyayı ekrana basıyor

Bu çok ciddi bir güvenlik hatasıdır.

Çünkü:

  • Kullanıcıya "hangi dosya yüklenecek" sorusu sorulmaz
  • Ama burada doğrudan sorulmuş

Bu noktada artık şunu biliyoruz:

Bu uygulamada File Inclusion zafiyeti olma ihtimali çok yüksek.

3. LFI mi, RFI mı? Nasıl Karar Verdik?

Önce mantıkla ilerleyelim.

RFI olabilmesi için:

  • PHP ayarında allow_url_include açık olmalı
  • Uygulama harici URL'leri include edebilmelidir

Deneme yapıyoruz:

page=http://example.com/test.txt

Sonuç:

  • Çalışmıyor
  • Herhangi bir içerik gelmiyor

Bu bize şunu gösterir:

Bu uygulama uzak dosya include etmiyor

Dolayısıyla:

  • Bu bir RFI değil
  • Geriye sadece LFI (Local File Inclusion) kalıyor

4. Peki Ne Yapmaya Çalışıyoruz?

Amaç şu:

Uygulamanın normalde göstermemesi gereken başka bir PHP dosyasını ekrana bastırmak

Ana sayfada home.php çalışıyorsa, muhtemelen:

  • admin.php gibi dosyalar da sunucuda vardır

Ama doğrudan şunu yazdığımızda:

page=admin.php

sayfa açılmıyor.

Bu çok önemli bir ipucu.

5. Burada Neden Çalışmadı?

Çünkü uygulama çok basit bir kontrol yapıyor.

Örneğin:

  • admin kelimesini engelliyor olabilir
  • Ya da sadece belirli desenleri kontrol ediyor olabilir

Ama kritik hata şu:

  • Bu kontrol yetersiz
  • Dosya yolu normalize edilmiyor

İşte burada path traversal mantığı devreye giriyor.

6. Neden ….//admin.php Çalıştı?

Asıl kilit nokta burası.

Normalde herkes şunu dener:

../admin.php

Ama bu labda bu çalışmıyor.

Çünkü uygulama:

  • ../ ifadesini kontrol ediyor
  • Ama farklı yazımları hesaba katmıyor

Şu payload ise çalışıyor:

page=….//admin.php
None

Sebebi çok net:

  • ….// ifadesi
  • Basit filtreleri bypass eder
  • PHP tarafından normalize edilir
  • Sunucu bunu gerçek dosya yolu gibi yorumlar

Yani uygulama:

  • "Tehlikeli değil" sanıyor
  • Ama PHP "Bu admin.php" diyor

Bu bir filter bypass örneğidir.

7. Burp Suite ile Deneme

Artık payload'ımız var ama bunu düzgün şekilde göstermek istiyoruz.

Adımlar:

Burp Proxy açıkken sayfayı yeniliyoruz

İstek yakalanıyor:

GET /index.php?page=home.php HTTP/1.1

page parametresini değiştiriyoruz:

GET /index.php?page=….//admin.php HTTP/1.1

İsteği gönderiyoruz

None

Learn the Capital 3

1. İlk Adım: Kaynak Kod Ne Yapıyor?

Labın kritik kısmı aşağıdaki koddur:

if(isset($_GET['country'])){
    $page = $_GET['country'];
    if ( !strstr($page , 'file')) {
        echo "ERROR: File not found!";
        exit;
    } 
    else{
        include($page);
    }
}

Burada dikkat edilmesi gereken nokta tek bir kontrolün var olmasıdır.

Uygulama şunu yapıyor:

  • country parametresini alıyor
  • Parametrenin içinde "file" kelimesi geçiyor mu diye bakıyor
  • Geçiyorsa → dosyayı include() ediyor
  • Geçmiyorsa → hata verip duruyor

Yani:

  • Dosya yolunun neresi olduğu kontrol edilmiyor
  • Dosyanın gerçekten güvenli olup olmadığı kontrol edilmiyor
  • Sadece string içinde "file" var mı bakılıyor

Bu, filtre değil; anahtar kelimeye dayalı zayıf bir güvenlik kontrolü.

2. Buradan Çıkan Mantıksal Sonuç

Bu noktada şu çıkarımı yapmak gerekiyor:

"Eğer içinde file kelimesi geçen bir dosya yolu verirsem, uygulama bunu sorgulamadan include edecek."

Yani yapılması gereken şey:

  • Filtrenin istediği koşulu bilerek sağlamak
  • Aynı anda istediğimiz dosyaya ulaşacak bir yol yazmak

Bu, doğrudan Local File Inclusion (LFI) zafiyetidir.

3. Hedef Ne?

Labın önceki aşamalarından ve dizin yapısından şu varsayım yapılabilir:

  • Uygulama dizininde admin.php isimli bir dosya mevcut
  • Bu dosya normalde arayüzden erişilebilir değil
  • Ama include() ile çağrılırsa içeriği ekrana basılacak

Dolayısıyla hedef:

admin.php dosyasını include ettirmek

4. Payload Nasıl Seçildi?

Payload tamamen şu iki şartı aynı anda sağlaması gerekiyordu:

  1. İçinde "file" kelimesi geçecek
  2. Dosya yolu admin.php'ye gidecek

Bu noktada directory traversal devreye girer.

5. Payload'ın Mantığı: file/../../admin.php

Seçilen payload:

file/../../admin.php

Bunu parçalara ayıralım:

file/

  • Uygulamanın aradığı zorunlu kelime
  • Güvenlik kontrolünü geçmek için bilinçli olarak eklendi

../../

  • Mevcut dizinden iki klasör yukarı çıkmayı sağlar
  • Gerçek dosya sisteminde admin.php'nin bulunduğu dizine ulaşmak için kullanılır

admin.php

  • Include edilmek istenen hedef dosya

Bu payload şunu yapar:

"Sana istediğin kelimeyi veriyorum ama dosya yolunu ben belirliyorum."

6. Neden Bu Payload Çalıştı?

Çünkü uygulama:

  • Dosya yolunu normalize etmiyor
  • Gerçek path kontrolü yapmıyor
  • Sadece string içinde "file" var mı diye bakıyor

include() fonksiyonu ise:

  • file/../../admin.php yolunu çözümler
  • Dizini normalize eder
  • Gerçek dosya sisteminde admin.php dosyasını bulur
  • Dosyayı çalıştırır

Sonuç olarak response içinde şu çıktı görülür:

Welcome to the Admin page..

Bu, dosyanın başarıyla include edildiğini gösterir.

None

7. Burp Üzerinden Doğrulama

Yakalanan normal istek:

GET /index.php?country=france.php

Değiştirilen parametre:

GET /index.php?country=file/../../admin.php

Sunucudan gelen response'ta admin içeriği yer alır. Bu noktada zafiyet kanıtlanmış ve lab çözülmüş olur.

None