September 3, 2026
File Upload Vulnerabilities
Web Güvenliği Serisi — Bölüm 5

By Yusufbarut
9 min read
Kullanıcıların dosya yükleyebilmesi, modern web uygulamalarında en yaygın özelliklerden biridir. Profil fotoğrafları, PDF belgeleri, CV yüklemeleri veya ürün görselleri gibi işlemler günlük hayatın bir parçasıdır. Ancak yüklenen dosyaların yeterince doğrulanmaması, saldırganların zararsız görünen bir dosya aracılığıyla sunucuda kod çalıştırmasına, hassas verilere erişmesine veya sistemi tamamen ele geçirmesine neden olabilir. İşte bu zafiyet sınıfı File Upload Vulnerabilities olarak adlandırılır.
Serinin önceki bölümlerinde XSS ile tarayıcıya, SQL Injection ile veritabanına, CSRF ile kullanıcının oturumuna, SSRF ile sunucunun ağına duyulan yanlış güveni konuşmuştuk. Bu bölümde mesele biraz daha çarpıcı: saldırgan artık sadece veri okumuyor ya da bir isteği yönlendirmiyor — doğrudan sunucunun üzerinde çalıştırılabilir kod bırakabiliyor. Doğru koşullarda bir file upload zafiyeti, tek bir dosya seçme kutusundan başlayıp tam bir sunucu ele geçirmeyle (Remote Code Execution) sonuçlanabilir.
Bu yazıda file upload zafiyetinin ne olduğunu, dosya yükleme sürecinin normalde nasıl işlediğini, saldırganların hangi yöntemleri kullandığını, en yaygın bypass tekniklerini, gerçek bir senaryoyu, PortSwigger laboratuvarlarında pratik yapmayı, Burp Suite ile test yöntemlerini ve son olarak nasıl korunulacağını ele alacağız.
Başlayalım.
File Upload Vulnerability Nedir?
File Upload Vulnerability, bir web uygulamasının kullanıcıdan aldığı dosyayı yeterince doğrulamadan sunucuya kaydetmesi ve bu dosyanın daha sonra beklenmeyen bir şekilde (örneğin kod olarak) işlenmesi sonucu ortaya çıkan zafiyet sınıfıdır.
Sorun genelde tek bir varsayımdan doğar: "Kullanıcı bir resim yüklüyor, o zaman gelen dosya bir resimdir." Oysa dosya uzantısı, MIME tipi ya da dosya adı gibi bilgilerin hepsi istemci tarafından belirlenir ve saldırgan tarafından istenildiği gibi değiştirilebilir. Sunucu bu bilgilere körü körüne güvenirse, aslında bir PHP betiği olan bir dosya "resim" kılığında sisteme girebilir.
Bu zafiyetin tehlikeli olmasının asıl sebebi şu: çoğu diğer web zafiyeti (XSS, SQLi gibi) veriyle sınırlı kalırken, file upload zafiyeti çoğu zaman doğrudan kod çalıştırmaya kapı aralar. Sunucuya bırakılan bir web shell, saldırgana o sunucu üzerinde komut çalıştırma, dosya sistemine erişme, iç ağı keşfetme ve genellikle tam kontrol imkânı verir.
Dosya Yükleme Süreci Nasıl Çalışır?
Önce işlerin nasıl yürümesi gerektiğine bakalım.
Standart, güvenli bir dosya yükleme akışında adımlar şöyledir:
- Kullanıcı bir dosya seçer (örneğin bir profil fotoğrafı).
- Dosya sunucuya gönderilir.
- Sunucu dosyayı çok yönlü olarak doğrular: uzantı, MIME tipi, dosya içeriği (magic bytes), boyut.
- Doğrulamadan geçen dosya, rastgele üretilmiş bir adla, web kök dizininin dışında bir yere kaydedilir.
- Dosya daha sonra kontrollü bir şekilde (örneğin bir CDN veya ayrı bir subdomain üzerinden) kullanıcıya sunulur.
Şimdi bu akışın saldırgan elinde nasıl bozulduğuna bakalım.
Zafiyetli bir sistemde akış şu hale gelir:
- Saldırgan bir "resim" yüklüyormuş gibi görünen ama aslında çalıştırılabilir kod içeren bir dosya hazırlar (
shell.phpgibi). - Sunucu, sadece uzantıya veya MIME başlığına bakarak (ya da hiç bakmayarak) dosyayı kabul eder.
- Dosya, web sunucusunun erişebildiği ve çalıştırabildiği bir dizine kaydedilir.
- Saldırgan, dosyanın yüklendiği URL'ye doğrudan bir tarayıcı isteğiyle ulaşır.
- Sunucu bu dosyayı bir veri değil, bir program olarak çalıştırır.
Fark, aradaki tek bir varsayımda gizli: dosyanın "veri" mi yoksa "kod" mu olarak yorumlanacağı. Bu ayrımı belirleyen şey dosyanın adı değil, sunucunun o dosyayı nasıl işlediğidir — ama pek çok uygulama bunu doğru şekilde ayıramaz.
Saldırganlar Hangi Yöntemleri Kullanır?
Bu yazıda ele alacağımız bypass teknikleri şunlar:
- Double Extension (
shell.php.jpg) - MIME Type Spoofing
- Magic Bytes Bypass
- SVG Upload (XSS ve XXE için taşıyıcı olarak)
- Null Byte (artık büyük ölçüde tarihsel bir bilgi olarak)
- ZIP Slip (ayrı bir başlıkta)
- Web Shell Upload
- Content-Type Manipülasyonu
- Dosya adı manipülasyonu
- Rastgele dosya adı üretiminin önemi
Bu tekniklerin çoğu, sunucunun doğrulama mantığındaki tek bir zayıf noktayı hedefler. Doğrulama ne kadar "yüzeysel" ise (sadece uzantıya bakmak, sadece MIME başlığına güvenmek gibi), bypass o kadar kolaydır. Şimdi bunları detaylandıralım.
En Yaygın File Upload Zafiyetleri
1. Double Extension (Çift Uzantı)
Bazı sunucular, dosya uzantısını kontrol ederken sadece dosya adının bir yerinde yasaklı bir uzantı olup olmadığına ya da son uzantıya bakar. Saldırgan bu mantığı image.php.jpg gibi bir isimle kandırabilir:
- Eğer sunucu sadece "yasaklı uzantı listesinde mi" diye bakıyorsa ve
.php.jpgbu listede yoksa, dosya kabul edilir. - Bazı sunucu yapılandırmalarında (özellikle eski Apache
AddHandlerayarlarıyla), dosya adında herhangi bir yerde.phpgeçmesi, dosyanın PHP olarak çalıştırılmasına yetebilir.
Sonuç: dosya adı bir resme benziyor, ama sunucu onu PHP kodu olarak yorumluyor.
2. MIME Type Spoofing (İçerik Türü Sahteciliği)
Tarayıcı, dosyayı yüklerken bir Content-Type başlığı gönderir (image/jpeg gibi). Bu değer tamamen istemci tarafından belirlenir — sunucu tarafında hiçbir doğrulama yapılmıyorsa, saldırgan bu başlığı dilediği gibi değiştirebilir. Burp Suite'te bir isteği yakalayıp Content-Type: image/jpeg yazıp içeriğinde PHP kodu bulunan bir dosya göndermek, bu bypass'ın en klasik örneğidir.
3. Magic Bytes Bypass
Daha dikkatli uygulamalar, dosyanın gerçekten iddia ettiği tipte olup olmadığını magic bytes (dosyanın ilk birkaç baytındaki imza) üzerinden kontrol eder. Örneğin bir PNG dosyası her zaman 89 50 4E 47 baytlarıyla başlar. Saldırgan, PHP payload'ının başına bu baytları ekleyerek dosyayı "gerçek bir PNG" gibi gösterebilir; sunucu imzayı doğrular ama dosyanın geri kalanındaki kodu görmezden gelir. Dosya, uzantısı .php olduğu sürece (veya çift uzantı gibi başka bir teknikle desteklenirse) yine çalıştırılabilir.
4. SVG Upload
SVG dosyaları aslında XML tabanlı metin dosyalarıdır ve <script> etiketleri içerebilir. Bir uygulama SVG'yi "resim" kategorisinde kabul edip tarayıcıda doğrudan render ediyorsa, saldırgan SVG içine gömdüğü script ile Stored XSS elde edebilir. Bazı SVG işleme kütüphaneleri ayrıca XML External Entity (XXE) saldırılarına da açık olabilir.
5. Null Byte Injection (Tarihsel)
Eski PHP sürümlerinde (5.3 öncesi), dosya adının içine %00 (null byte) enjekte etmek, string işlemenin dosya adını orada kesmesine neden oluyordu. Örneğin shell.php%00.jpg, sunucu tarafında shell.php olarak kaydediliyordu. Bu teknik günümüz sistemlerinde artık çalışmıyor, ama file upload zafiyetlerinin tarihini anlamak açısından bilinmesi faydalı.
6. ZIP Slip
Bir uygulama kullanıcının yüklediği bir ZIP arşivini sunucu tarafında açıyorsa (extract ediyorsa), arşiv içindeki dosya adlarına ../../ gibi path traversal dizileri yerleştirilebilir. Doğru şekilde doğrulanmayan bir extract işlemi, saldırganın dosyaları hedef dizinin dışına, örneğin web kök dizinine veya sistem dizinlerine yazmasına izin verebilir.
7. Web Shell Upload
Yukarıdaki tekniklerin çoğunun nihai amacı aynıdır: sunucuya bir web shell yerleştirmek. Web shell, sunucuda çalıştığında saldırgana HTTP istekleri üzerinden komut çalıştırma imkânı veren küçük bir betiktir. PortSwigger'ın kendi eğitim materyallerinde de kullanılan klasik örnek şu satırdır:
<?php echo system($_GET['cmd']); ?><?php echo system($_GET['cmd']); ?>Bu dosya sunucuya yüklenip çalıştırılabilir hale geldiğinde, saldırgan ?cmd=whoami gibi bir sorgu string'iyle sunucu üzerinde rastgele komut çalıştırabilir. Buradan sonrası çoğunlukla sunucunun tam ele geçirilmesiyle sonuçlanır.
Aşağıdaki tablo, bu tekniklerin özetini sunuyor:
Teknik Hedeflediği Zayıflık Sonuç Double Extension Uzantı kontrolünün yüzeysel olması Kod dosyası "resim" gibi kabul edilir MIME Spoofing Content-Type başlığına güven Sahte tip bildirimi kabul edilir Magic Bytes Bypass Sadece dosya başının kontrol edilmesi Payload dosya içine gizlenir SVG Upload SVG'nin "resim" sayılması Stored XSS / XXE Null Byte (tarihsel) Eski string işleme davranışı Uzantı kontrolü atlanır ZIP Slip Arşiv çıkarmada yol doğrulaması eksikliği Dosya sistem dışına yazılır Web Shell Kodun çalıştırılabilir dizine kaydedilmesi Uzaktan komut çalıştırma
Gerçek Bir Senaryo
Diyelim ki bir kurumsal İK platformu, adaylardan CV yüklemelerini istiyor. Yükleme formu şu kontrolü yapıyor:
İzin verilen uzantılar: .pdf, .doc, .docx, .jpg, .pngİzin verilen uzantılar: .pdf, .doc, .docx, .jpg, .pngKontrol, dosya adının sonundaki uzantıya bakıyor ve dosya adında bu uzantılardan biri geçiyorsa isteği kabul ediyor. Saldırgan, CV'sini cv_John_Doe.pdf.php adıyla yüklüyor.
Sunucu tarafında çalışan kontrol mantığı, dosya adında .pdf geçtiğini görüp isteği onaylıyor. Ancak Apache yapılandırmasında .php uzantılı her dosyanın PHP olarak yorumlanacağı tanımlı olduğundan — dosya adı .pdf ile "bitmiyor" gibi görünse de düzenli ifade tabanlı kontrol bunu yakalayamıyor — dosya, PHP motoru tarafından çalıştırılabilir hale geliyor.
Dosya, uygulamanın /uploads/cv/ dizinine, orijinal adıyla kaydediliyor. Bu dizin web kök dizininin altında ve dışarıdan doğrudan erişilebilir durumda. Saldırgan, tarayıcısından şu adrese gidiyor:
https://ik-platformu.com/uploads/cv/cv_John_Doe.pdf.php?cmd=whoamihttps://ik-platformu.com/uploads/cv/cv_John_Doe.pdf.php?cmd=whoamiDosya içeriğinde daha önce bahsettiğimiz basit web shell koduna benzer bir yapı bulunuyor. Sunucu bu isteği aldığında dosyayı PHP olarak çalıştırıyor ve whoami komutunun çıktısını saldırgana döndürüyor. Buradan itibaren saldırgan, sunucu üzerinde komut çalıştırma yetkisine sahip: dosya sistemini keşfedebilir, veritabanı bağlantı bilgilerini içeren config dosyalarını okuyabilir, iç ağı taramaya başlayabilir.
Bu senaryodaki kritik hata üç katmanlı: (1) uzantı kontrolü yüzeysel, (2) dosya içeriği hiç doğrulanmamış, (3) yüklenen dosyalar çalıştırılabilir bir dizine, orijinal adlarıyla kaydedilmiş. Üç katmandan sadece biri doğru yapılandırılmış olsaydı, bu saldırı zinciri kırılırdı.
PortSwigger Laboratuvarı
Bu konuda pratik yapmak isteyenler için PortSwigger Web Security Academy'nin File Upload Vulnerabilities bölümü, gerçek uygulamalara oldukça yakın senaryolar sunuyor. Özellikle şu laboratuvarlar öne çıkıyor:
- Web shell upload via unrestricted file upload — Hiçbir doğrulama yapmayan, en temel senaryo. Dosya doğrudan
.phpuzantısıyla yükleniyor ve çalıştırılıyor. - Web shell upload via Content-Type restriction bypass — Sunucu sadece
Content-Typebaşlığını kontrol ediyor; Burp Repeater'da bu başlığıimage/jpegolarak değiştirmek yeterli oluyor. - Web shell upload via path traversal — Dosya adı alanına path traversal dizileri (../../) enjekte edilerek dosyanın beklenmedik bir dizine, çalıştırılabilir bir konuma yazılması sağlanıyor.
- Web shell upload via extension blacklist bypass — Sunucunun kara listesinde olmayan daha az bilinen çalıştırılabilir uzantılar (
.phtml,.php5gibi) veya büyük/küçük harf oyunlarıyla (.pHp) filtre atlatılıyor. - Web shell upload via obfuscated file extension — Boşluk, çoklu nokta, URL encode edilmiş karakterler gibi dosya adı manipülasyonlarıyla filtre şaşırtılıyor.
- Remote code execution via polyglot web shell upload — Hem geçerli bir resim hem de geçerli bir PHP dosyası olarak yorumlanabilen "polyglot" bir dosya hazırlanarak, hem magic byte kontrolünü hem de uzantı kontrolünü aynı anda atlatmak hedefleniyor.
Genel akış her laboratuvarda benzer şekilde ilerliyor: yükleme isteği Burp'te yakalanır, doğrulamayı tetikleyen alan (uzantı, MIME tipi, dosya adı, dosya içeriği) tespit edilir, bu alan değiştirilerek istek tekrar gönderilir ve dosyanın yüklendiği URL'ye erişilerek çalıştırılması doğrulanır.
Burp Suite ile Test
Gerçek bir uygulamada file upload zafiyeti ararken izlenebilecek pratik bir metodoloji:
1. Önce normal bir dosya yükleyin ve isteği yakalayın. Proxy sekmesinde isteği inceleyin: dosya adı, Content-Type başlığı, form alanları nasıl gönderiliyor, dosyanın yüklendiği URL nasıl belirleniyor — hepsini not edin.
2. İsteği Repeater'a gönderip varyasyonlar deneyin.
- Dosya uzantısını
.php,.phtml,.php5,.phargibi alternatiflerle değiştirin. Content-Typebaşlığınıimage/jpegveyaimage/pngolarak bırakıp dosya içeriğini değiştirin.- Dosya adına çift uzantı ekleyin:
shell.php.jpg. - Magic bytes ekleyerek dosyanın başını gerçek bir resim gibi gösterin, payload'ı sonrasına yerleştirin.
3. Yüklenen dosyanın nerede saklandığını bulun. Yanıtta dönen URL'yi takip edin. Bazı uygulamalar dosya yolunu doğrudan yanıtta döndürür; bazılarında ise dosya adını tahmin etmek (orijinal ad, zaman damgası, artan sayaç gibi) gerekebilir.
4. Dosyaya doğrudan erişip çalıştığını doğrulayın. Eğer dosya çalıştırılabilir bir konuma yüklendiyse ve sunucu onu kod olarak yorumluyorsa, dosyaya yapılan bir GET isteği bunu ortaya çıkarır. Blind senaryolarda (yanıt görünmüyorsa) zamanlama farkları veya kendi sunucunuza giden bir out-of-band isteği (SSRF bölümünde bahsettiğimiz Collaborator gibi) kullanılabilir.
5. Farklı doğrulama katmanlarını tek tek test edin. Bir uygulamanın birden fazla kontrolü olabilir (uzantı + MIME + magic bytes). Her katmanı ayrı ayrı test edip hangisinin eksik veya hatalı yapılandırıldığını belirlemek, en etkili yöntemdir — çünkü genelde tüm katmanları aynı anda atlatmak yerine, en zayıf halkayı bulup ona odaklanmak yeterli olur.
Korunma Yöntemleri
File upload zafiyetlerine karşı savunma da, SSRF'de olduğu gibi katmanlı olmak zorunda. Tek bir kontrol asla yeterli değildir.
🛡 1. Allowlist kullanın, blocklist değil. "Şu uzantılara izin ver" demek, "şu uzantıları yasakla" demekten çok daha güvenlidir. Blocklist'ler .phtml, .php5, .pHp gibi varyasyonlarla neredeyse her zaman atlatılabilir.
🛡 2. Dosya içeriğini doğrulayın (Magic Byte Validation). Sadece uzantıya değil, dosyanın gerçek içeriğine bakın. Bir kütüphane kullanarak dosyanın gerçekten iddia ettiği formatta olup olmadığını (görüntü işleme kütüphanesiyle yeniden encode etmek dahil) doğrulayın. Bir resmi yeniden encode etmek, içine gömülü olabilecek çoğu payload'ı da etkisiz hale getirir.
🛡 3. MIME tipini sunucu tarafında belirleyin. İstemcinin gönderdiği Content-Type başlığına güvenmeyin; dosyanın gerçek içeriğinden MIME tipini sunucu tarafında tespit edin.
🛡 4. Rastgele dosya adları üretin. Kullanıcının verdiği dosya adını asla doğrudan kullanmayın. Sunucu tarafında rastgele bir tanımlayıcı (UUID gibi) üretip dosyayı bu adla kaydedin. Bu, hem double extension hem de path traversal saldırılarını büyük ölçüde etkisiz kılar.
🛡 5. Dosyaları web kök dizininin dışında saklayın. Yüklenen dosyalar, web sunucusunun doğrudan çalıştırabileceği bir dizinde tutulmamalı. Mümkünse ayrı bir depolama servisinde (S3 gibi) veya en azından çalıştırma izni olmayan bir dizinde saklanmalı.
🛡 6. Yükleme dizininde script çalıştırmayı devre dışı bırakın. Web sunucusu yapılandırmasında (Apache, Nginx) yükleme dizini için script çalıştırma iznini açıkça kapatın. Bu, üstteki kontrollerin hepsi atlatılsa bile son bir güvenlik katmanı sağlar.
🛡 7. Antivirus / malware tarama entegre edin. Yüklenen dosyaları, kaydetmeden önce bilinen zararlı imzalara karşı tarayan bir katman eklemek, özellikle çok kullanıcılı platformlarda ek bir güvenlik sağlar.
🛡 8. Least Privilege prensibini uygulayın. Web sunucusu prosesinin, yükleme dizinine yazma dışında minimum yetkiyle çalıştığından emin olun. Bir web shell çalıştırılsa bile, prosesin sistem üzerindeki yetkisi ne kadar kısıtlıysa saldırganın hareket alanı o kadar daralır.
🛡 9. ZIP/arşiv dosyalarını dikkatli işleyin. Sunucu tarafında bir arşivi açıyorsanız, her dosya yolunun hedef dizinin dışına çıkmadığını (path traversal içermediğini) mutlaka doğrulayın.
Sonuç
File upload zafiyetleri, serinin bu noktaya kadar işlediği diğer konulardan bir adım öteye geçiyor: veri sızdırmak veya bir isteği yönlendirmekle kalmıyor, doğrudan sunucu üzerinde kod çalıştırmaya kapı aralıyor. Tehlikeli olmasının sebebi karmaşık bir teknik gerektirmesi değil — tam tersine, çoğu zaman tek bir yüzeysel kontrolün (sadece uzantıya bakmak, sadece MIME başlığına güvenmek) yeterince derinlemesine düşünülmemiş olması.
Savunma tarafında da aynı ders geçerli: uzantı kontrolü, içerik doğrulaması, rastgele dosya adlandırma, çalıştırma izinlerinin kapatılması ve dizin izolasyonu bir arada çalıştığında, tek bir zayıf halka artık tüm zinciri kırmaya yetmiyor.
Serinin bir sonraki bölümünde farklı bir başlığa geçiyoruz. Hangi zafiyeti görmek istediğinizi yorumlarda yazabilirsiniz.
Kaynaklar
- PortSwigger Web Security Academy — File upload vulnerabilities
- OWASP — Unrestricted File Upload
- OWASP — File Upload Cheat Sheet
- PayloadsAllTheThings — Upload Insecure Files
📚 Bu Serinin Diğer Yazıları
- ✅Web Güvenliği Serisi — Bölüm 1: Cross-Site Scripting (XSS)
- ✅Web Güvenliği Serisi — Bölüm 2: SQL Injection
- ✅Web Güvenliği Serisi — Bölüm 3: Cross-Site Request Forgery (CSRF)
- ✅Web Güvenliği Serisi — Bölüm 4: Server-Side Request Forgery (SSRF)
- ✅Web Güvenliği Serisi — Bölüm 5: File Upload Vulnerabilities (Bu Yazı)
- ⏳ Web Güvenliği Serisi — Bölüm 6: Command Injection (Yakında)