İçeriğe geç
wedevit

21 Eylül 2026 · 8 dk okuma · yazılım

İlhan Buğra Aslan

Dosya yükleme özelliği nasıl kurulur? Boyut limiti, doğrulama ve güvenli saklama


Dosya yükleme, şartnamede tek satırla geçen ama dört ayrı karar gerektiren bir özelliktir: baytlar hangi yoldan geçecek, ne kabul edeceksiniz, nereye ve hangi adla yazacaksınız, kullanıcıya nasıl geri sunacaksınız. Kısa cevap: baytları mümkünse uygulama sunucunuzdan geçirmeyin, kabul edilecek uzantıları beyaz liste olarak tanımlayın, dosyayı kullanıcının verdiği adla saklamayın ve yüklenen hiçbir dosyayı ana alan adınızdan servis etmeyin. Bu dördünü ilk günde kurmak birkaç günlük iştir. Sonradan kurmak, üretimdeki her dosyayı taşımak demektir.

Konunun ciddiyeti ölçülebilir bir yerde duruyor. MITRE ve CISA'nın 11 Aralık 2025'te yayımladığı 2025 CWE Top 25 listesinde "tehlikeli türde dosyanın sınırsız yüklenmesi" (CWE-434) 12. sırada. Aynı listenin 25. sırasında da sınır ve kısıt konmadan kaynak tahsisi (CWE-770) var, ki pratikte çoğu zaman "boyut limiti koymamak" demektir. Yükleme formu, kimliği doğrulanmamış bir kullanıcının altyapınıza dosya yazabildiği nadir noktalardan biri. Ona göre davranmak gerekiyor.

Baytlar hangi yoldan geçecek?

İlk ve en belirleyici karar bu. Klasik yol, dosyanın uygulama sunucunuza gelmesi, orada doğrulanması ve oradan depolamaya yazılmasıdır. Yazması kolaydır, akışın tamamını görürsünüz, küçük dosyalarda gayet iyi çalışır. Sorun, yolun üstündeki her katmanın kendi limitini dayatması. nginx'te client_max_body_size varsayılanı 1 MB'dır ve aşıldığında istemci 413 alır. PHP kurulumları varsayılan olarak upload_max_filesize 2M, post_max_size 8M ile gelir. Vercel'de bir fonksiyonun istek gövdesi 4,5 MB'ı geçemez, geçince FUNCTION_PAYLOAD_TOO_LARGE hatası döner. Önde Cloudflare varsa istek gövdesi Free ve Pro planlarda 100 MB, Business'ta 200 MB, Enterprise'ta varsayılan 500 MB ile sınırlıdır; Enterprise müşterileri 4 Eylül 2026'dan beri bu sınırı panelden 5 GB'a kadar kendileri çıkarabiliyor.

İkinci yol, tarayıcının doğrudan depolamaya yazması. Akış şöyle işler: tarayıcı uygulamanızdan izin ister, uygulama kullanıcının yetkisini kontrol edip kısıtları gömülü bir imzalı (presigned) adres üretir, tarayıcı baytları doğrudan S3, R2 ya da Blob servisine gönderir, uygulama yalnızca "şu kullanıcı şu dosyayı yükledi" kaydını tutar. Uygulama sunucunuz büyük dosya trafiğinden tamamen kurtulur. Birkaç megabaytın üstünde bir şey yükletecekseniz varsayılan tercihiniz bu olmalı.

Doğrudan yüklemenin gizli tuzağı

Sunucunuz baytları görmüyorsa boyutu ve türü de göremez. Buradaki klasik hata, imzalı adresi üretip gerisini tarayıcıya bırakmak. O adres eline geçen biri, sizin 5 MB beklediğiniz yere 5 GB yazabilir. Çözüm, kısıtları imzanın içine koymak. S3'ün presigned POST politikasındaki content-length-range koşulu tam olarak bunun için var: alt ve üst sınırı bayt cinsinden yazarsınız, S3 gelen Content-Length değerini karşılaştırır ve aralık dışındaysa isteği 400 ile reddeder. Aynı politikada nesne anahtarını, izin verilen içerik türünü ve şifreleme ayarını da sabitleyebilirsiniz.

İkinci kural: bir nesne, uygulamanız onaylayana kadar gerçek sayılmasın. Tarayıcının "yükleme bitti" demesine güvenmeyin, çünkü o çağrı hiç gelmeyebilir ya da baytlar hiç gönderilmemiş olabilir. Depolama tarafındaki olay bildirimini dinleyin veya sunucudan bir HEAD isteğiyle nesnenin varlığını ve boyutunu doğrulayın. İmzalı adresin ömrü de bir karardır. SigV4 ile en fazla 7 gün verilebilir (konsolda üretilen bağlantılarda tavan 12 saat), ama bir yükleme bağlantısının 15 dakikadan uzun yaşaması için sebep yoktur. Bir rol üstlenerek üretilen imzalar rol oturumu bittiğinde geçersiz olur; "7 gün verdim ama 1 saatte kırıldı" şikayetlerinin kaynağı neredeyse her zaman budur.

Boyut limiti tek bir yerde durmaz

Limit üç yerde birden yaşar ve üçü de gereklidir. Tarayıcıda, kullanıcı 400 MB'lık dosyayı 10 dakika boyunca yükleyip en sonda hata almasın diye. Kenarda ya da ters vekil sunucuda, kötü niyetli isteği hattın başında kesmek için. Depolama politikasında, çünkü asıl kapı orası. Bu üç sayı birbirini tutmuyorsa kullanıcı, nerede kesildiğini anlamadığı bir hata alır.

Gerçekten büyük dosyalar için rakamlar şöyle. S3'e tek istekle en fazla 5 GiB yazabilirsiniz, üstü için çok parçalı yükleme gerekir; AWS zaten 100 MB'tan itibaren çok parçalıya geçmeyi öneriyor. Parça boyutu 5 MiB ile 5 GiB arasında, bir yüklemede en fazla 10.000 parça olabilir. Cloudflare R2 tarafında tek seferlik yükleme sınırı yine 5 GiB, çok parçalı ile nesne 5 TiB'a kadar çıkıyor, parça sayısı tavanı 10.000. Kesintiden sonra kaldığı yerden devam eden yüklemeler için tus protokolü ya da depolamanın çok parçalı API'si kullanılır. Bu işin standart hali hâlâ yolda: IETF'in draft-ietf-httpbis-resumable-upload taslağı 6 Temmuz 2026'da 12. sürümüne ulaştı, henüz RFC değil. Sahadaki ekip mobil hattan 500 MB'lık video yüklüyorsa devam edebilen yükleme şart. Ofisten 2 MB PDF yükleyen muhasebe için gereksiz karmaşa.

Neyi kabul ettiğinize kim karar veriyor?

Doğrulamanın tek kuralı var: istemciden gelen hiçbir bilgi dosyanın ne olduğunu söylemez. OWASP'ın File Upload Cheat Sheet'i bunu maddelerle anlatıyor, özeti şöyle:

  • Uzantı için beyaz liste kullanın. Kara liste tutmaya çalışanlar .jpg.php, .phtml, .pHp, çift uzantı ve null byte gibi onlarca varyantla uğraşmak zorunda kalır. İşinizin gerektirdiği uzantıları sayın, gerisini reddedin.
  • Content-Type başlığına güvenmeyin. İstemci gönderir, sahtesini yazmak tek satırlık iştir. Yanlış dosyayı kazayla yükleyen kullanıcıyı uyarmaya yarar, saldırganı durdurmaya yaramaz.
  • Dosya imzasını (sihirli baytları) kontrol edin. Gerekli bir adım, ama tek başına yeterli değil: geçerli bir JPEG başlığıyla başlayıp içinde çalıştırılabilir kod taşıyan polyglot dosyalar yapmak zor değil.
  • Görselleri yeniden kodlayın. OWASP'ın image rewriting dediği yöntem, gelen görseli açıp kendi kütüphanenizle yeniden yazmaktır. Gömülü yük, fazladan metadata ve bozuk yapı bu işlemde düşer. Zaten küçük resim üretiyorsanız maliyeti sıfıra yakındır.
  • SVG'yi görsel saymayın. SVG bir belgedir, içinde script çalışır. Ya güvenilir bir kütüphaneyle sterilize edin ya da yalnızca indirilebilir dosya olarak sunun.
  • Arşiv ve ofis dosyalarında bir katman daha var. Zip için açılmış boyutu sınırlayın (sıkıştırma bombası) ve arşiv içindeki yolları temizleyin (zip slip). Ofis dosyalarında makro riski devam eder.

Saklarken: ad bir veridir, yol değil

Kullanıcının verdiği dosya adı ekranda gösterilecek bir metindir, dosya sisteminde bir yol değil. Nesneyi UUID gibi rastgele bir anahtarla saklayın, orijinal adı ayrı bir sütunda tutun ve indirme anında başlıkla geri verin. Bu tek alışkanlık, yol atlama (../../), üzerine yazma ve işletim sistemine özgü ad tuhaflıklarının tamamını bitirir.

Depolama yeri için OWASP'ın sıralaması net: en iyisi dosyaları bambaşka bir sunucuda tutmak, olmuyorsa webroot dışında, o da olmuyorsa webroot içinde çok sıkı izinlerle. Kova varsayılan olarak kapalı olsun; internete açık kalmış depolama kovaları bulut tarafındaki en yaygın kaza türlerinden biri, ayrıntısını bulut yanlış yapılandırma ve paylaşılan sorumluluk yazımızda ele almıştık. Depolama erişim anahtarlarının koda gömülmemesi de aynı hikayenin parçası, o konuyu da sır yönetimi yazımızda anlatmıştık.

Sunarken: neden ayrı bir alan adı?

GitHub kullanıcı dosyalarını raw.githubusercontent.com üzerinden, Google googleusercontent.com üzerinden verir. Sebep kozmetik değil. Kullanıcının yüklediği bir HTML ya da SVG dosyası ana alan adınızdan servis edilirse, o dosya sizin oturum çerezlerinizin bulunduğu kökende çalışır; yani kalıcı bir XSS'e dönüşür. Ayrı bir alan adı ya da en azından ayrı bir alt alan adı bu bağı koparır.

Servis ederken üç başlık işinizi görür: Content-Type değerini kullanıcının söylediğinden değil kendi kaydınızdan yazın, X-Content-Type-Options: nosniff ile tarayıcının tür tahmin etmesini kapatın, indirilmesi gereken dosyalarda Content-Disposition: attachment kullanın. Bir de her indirmede yetki kontrolü: /files/1234 adresindeki sayıyı bir artırıp başkasının faturasını indirebilen bir sistem, yükleme tarafını ne kadar sıkı yaparsa yapsın açıktır. Yetkinin nerede ve nasıl kontrol edileceği konusunu yetkilendirme modeli yazımızda açmıştık. Özel dosyaları CDN arkasına koyarken önbellek ayarlarına iki kez bakın; yanlış bir Cache-Control, bir kullanıcının belgesini diğerine gösterebilir. Önbellek stratejisi yazımızda bu tuzakları örneklemiştik.

Tarama ve karantina

Kullanıcıların birbirine dosya gönderdiği bir sistem işletiyorsanız zararlı yazılım taraması tercih değil gereklilik. Çalışan desen basit: dosya önce karantina kovasına yazılır, taramadan temiz çıkarsa asıl kovaya taşınır, kirliyse silinir ve kullanıcı bilgilendirilir. Kendi kuracak ekipler ClamAV kullanıyor; yönetilen tarafta AWS'in 11 Haziran 2024'te genel kullanıma açtığı GuardDuty Malware Protection for S3 seçilen kovaya yeni yüklenen nesneleri otomatik tarıyor. İki durumda da tarama asenkrondur, yani kayıt bir süre "taranıyor" durumunda kalır ve o süre boyunca dosya kimseye servis edilmez. Bu tür işleri istek hattının dışına almanın yolu için arka plan işleri ve kuyruk mimarisi yazımıza bakabilirsiniz.

Kimsenin konuşmadığı kısım: sahipsiz dosyalar

Yükleme özelliği canlıya çıktıktan altı ay sonra kovada üç tür çöp birikir. Yarım kalmış yüklemeler, silinen kayıtların geride bıraktığı dosyalar ve aynı belgenin sekiz kopyası. Üçü de fatura yazar, üçü de yedeklere sızar. Çözümü ilk günde kurulur: yarım kalan çok parçalı yüklemeleri temizleyen bir yaşam döngüsü kuralı, kayıt silindiğinde nesneyi de silen bir iş ve hesap başına bir kota. Depolamanın kendisi genelde ucuzdur, pahalı olan dışarı veri çıkışıdır; maliyetin nerede biriktiği konusunu bulut maliyeti optimizasyonu yazımızda ölçmüştük. Yüklenen dosyalar çoğu zaman kişisel veri içerdiği için saklama süresini ve silme talebine cevap verme yolunu ürünün içine baştan koymak, sonradan eklemekten belirgin şekilde ucuza gelir.

Devreye almadan önce sekiz kontrol

  1. Baytlar uygulama sunucusundan geçiyor mu? Geçiyorsa yoldaki tüm limitleri (proxy, çalışma zamanı, CDN) yazın ve birbirini tuttuğundan emin olun.
  2. İmzalı adresin içinde boyut ve tür kısıtı var mı? Yoksa limitiniz yok demektir.
  3. İmzalı adresin ömrü kaç dakika? Yükleme için 15 dakika, indirme için birkaç dakika çoğu senaryoya yeter.
  4. Uzantı beyaz listesi ve imza kontrolü ikisi birden var mı? Görselleri yeniden kodluyor musunuz?
  5. Dosya rastgele bir anahtarla mı saklanıyor? Kullanıcının verdiği ad sadece görüntüleme için mi kullanılıyor?
  6. Kova kapalı mı, dosyalar ayrı bir alan adından mı servis ediliyor? İndirmede yetki kontrolü her istekte çalışıyor mu?
  7. Tarama var mı, tarama bitene kadar dosya erişime kapalı mı?
  8. Sahipsiz dosyaları kim temizliyor? Yaşam döngüsü kuralı ve hesap başına kota tanımlı mı?

Bu sekiz maddeyi mevcut sisteminiz üzerinde bir saat içinde geçebilirsiniz; çoğu ekip ilk üçünde takılıyor. Wedevit olarak dosya akışı olan ürünlerde yükleme yolunu baştan sona çıkarıyor, limitleri ve doğrulamayı tek tek yerine koyuyor, saklama ve servis katmanını yetkilendirmeyle birlikte kuruyoruz. İhtiyaç varsa yükleme uçlarını API güvenliği tarafındaki kontrollerle birlikte test ediyoruz. Tüm çalışma uzaktan yürür.


Bu konuda yardıma mı ihtiyacınız var?

iletişime geç →← tüm yazılar