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

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.phpgibi 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.phpKaynak koddan şu çıkarım yapılır:
"Sunucu
countryparametresinin içeriğini sorgulamıyor, sadece include ediyor."
Bu durumda mantıksal soru şudur:
Eğer
france.phpyerine başka bir PHP dosyası gönderilirse ne olur?
Burp Suite ile Exploitation Mantığı
country=../admin.phpGönderilen istek:
GET /index.php?country=../admin.php HTTP/1.1
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ı:
$_GET['country']alındı- Değer
admin.php include(admin.php)çalıştırıldı- 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.

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.phpBurada çok önemli bir detay var:
pageadı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_includeaçık olmalı - Uygulama harici URL'leri include edebilmelidir
Deneme yapıyoruz:
page=http://example.com/test.txtSonuç:
- Ç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.phpgibi dosyalar da sunucuda vardır
Ama doğrudan şunu yazdığımızda:
page=admin.phpsayfa açılmıyor.
Bu çok önemli bir ipucu.
5. Burada Neden Çalışmadı?
Çünkü uygulama çok basit bir kontrol yapıyor.
Örneğin:
adminkelimesini 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.phpAma 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
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.1page parametresini değiştiriyoruz:
GET /index.php?page=….//admin.php HTTP/1.1İsteği gönderiyoruz

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:
countryparametresini 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
filekelimesi 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.phpisimli 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.phpdosyasını include ettirmek
4. Payload Nasıl Seçildi?
Payload tamamen şu iki şartı aynı anda sağlaması gerekiyordu:
- İçinde "file" kelimesi geçecek
- Dosya yolu
admin.php'ye gidecek
Bu noktada directory traversal devreye girer.
5. Payload'ın Mantığı: file/../../admin.php
Seçilen payload:
file/../../admin.phpBunu 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.phpyolunu çözümler- Dizini normalize eder
- Gerçek dosya sisteminde
admin.phpdosyası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.

7. Burp Üzerinden Doğrulama
Yakalanan normal istek:
GET /index.php?country=france.phpDeğiştirilen parametre:
GET /index.php?country=file/../../admin.phpSunucudan gelen response'ta admin içeriği yer alır. Bu noktada zafiyet kanıtlanmış ve lab çözülmüş olur.
