Web uygulamalarında güvenli dosya yüklemeleri için sıkı izinler, boyut sınırları, imzalı URL’ler ve temel kötü amaçlı yazılım tarama desenleri gereklidir.
Dosya yüklemeler masum görünebilir: bir profil fotoğrafı, bir PDF, bir tablo. Ancak sıklıkla ilk güvenlik olayı olurlar çünkü yabancıların sisteminize gizemli bir kutu göndermesine izin verirler. Kabul ederseniz, depolarsanız ve başkalarına gösterirseniz, uygulamanıza yeni bir saldırı yolu açmış olursunuz.
Risk sadece “birisi virüs yükledi” değil. Kötü bir yükleme özel dosyaları sızdırabilir, depolama faturanızı uçurabilir veya kullanıcıları erişim vermeye ikna edebilir. “invoice.pdf” adlı bir dosya aslında PDF olmayabilir. Gerçek PDF ve resimler bile, uygulamanız meta verilere güveniyorsa, önizlemeyi otomatik oluşturuyorsa veya yanlış kurallarla sunuyorsa sorun çıkarabilir.
Gerçek hatalar genellikle şöyle görünür:
Bir detay birçok olayı tetikler: dosyaları depolamak, dosyaları sunmakla aynı şey değildir. Depolama baytları tuttuğunuz yerdir. Sunma, bu baytların tarayıcılara ve uygulamalara nasıl teslim edildiğidir. İşler ters gittiğinde, uygulama kullanıcı yüklemelerini ana siteyle aynı güven düzeyi ve kurallarla sunar; bu durumda tarayıcı yüklemeyi "güvenilir" olarak kabul eder.
Küçük veya büyüyen bir uygulama için “yeterince güvenli” genellikle dört soruyu elinizde net yanıtla cevaplayabilmeniz demektir: kim yükleyebilir, ne kabul ediyorsunuz, ne kadar büyük/ne sıklıkta ve kim daha sonra okuyabilir. Hızla inşa ediyorsanız bile (üretilmiş kod veya sohbet tabanlı platformla), bu koruyucular önem taşır.
Her yüklemeyi güvensiz girdi olarak ele alın. Yüklemeleri güvenli tutmanın pratik yolu, kimin bunları kötüye kullanabileceğini ve onlar için “başarı”nın ne olduğunu görselleştirmektir.
Çoğu saldırgan ya zayıf yükleme forlarını tarayan botlardır ya da ücretsiz depolama, veri kazıma veya servis trolleme amacıyla sınırları zorlayan gerçek kullanıcılardır. Bazen de rakipler sızıntı veya kesintileri deniyor olabilir.
Ne istiyorlar? Genellikle şu sonuçlardan biri:
Sonra zayıf noktaları haritalandırın. Yükleme uç noktası ön kapıdır (aşırı büyük dosyalar, tuhaf formatlar, yüksek istek oranları). Depolama arka odadır (açık bucket’lar, yanlış izinler, paylaşılan klasörler). İndirme URL’leri ise çıkıştır (öngörülebilir, uzun ömürlü veya kullanıcıyla bağlanmamış).
Örnek: bir “özgeçmiş yükleme” özelliği. Bir bot binlerce büyük PDF yükleyip maliyeti artırırken, kötü niyetli bir kullanıcı bir HTML dosyası yükler ve bunu “belge” olarak paylaşarak başkalarını kandırır.
Kontroller eklemeden önce, uygulamanız için en önemli olanı belirleyin: gizlilik (kim okuyabilir), kullanılabilirlik (sunmaya devam edebilir misiniz), maliyet (depolama ve bant genişliği) ve uyumluluk (verinin nerede saklandığı ve ne kadar süre tutulduğu). Bu öncelik listesi kararları tutarlı kılar.
Çoğu yükleme olayı karmaşık hack’ler değil. Bunlar basit “başkasının dosyasını görebiliyorum” hatalarıdır. İzinleri yüklemelerin bir parçası olarak ele alın, sonradan eklenen bir özellik gibi değil.
Bir kuralla başlayın: varsayılan reddet. Her yüklenen nesnenin özel olduğunu, açıkça izin verene kadar kabul edin. “Varsayılan olarak özel” fatura, sağlık dosyaları, hesap belgeleri ve kullanıcıya bağlı her şey için güçlü bir temel sağlar. Dosyaları yalnızca kullanıcı açıkça beklediğinde genel yapın (ör. herkese açık avatar) ve yine de zaman sınırlı erişimi düşünün.
Rolleri basit ve ayrı tutun. Yaygın bir ayrım:
/user-uploads/ gibi klasör düzeyinde kurallara güvenmeyin. Okuma zamanında her dosya için sahipliği veya tenant erişimini kontrol edin. Bu, birisi takım değiştirip ayrıldığında ya da dosya yeniden atandığında sizi korur.
İyi bir destek deseni dar ve geçicidir: belirli bir dosyaya erişim verin, kaydedin ve otomatik olarak süresi dolsun.
Çoğu yükleme saldırısı basit bir numara ile başlar: dosya isme veya tarayıcı başlığına bakınca güvenli gibi görünür ama aslında farklıdır. İstemcinin gönderdiği her şeyi güvensiz kabul edin.
Bir allowlist ile başlayın: kabul ettiğiniz tam formatları belirleyin (örneğin .jpg, .png, .pdf) ve diğerlerini reddedin. “Herhangi bir resim” veya “herhangi bir belge” demekten kaçının, gerçekten ihtiyaç yoksa.
Dosya uzantısına veya istemciden gelen Content-Type başlığına güvenmeyin. İkisi de kolayca taklit edilebilir. invoice.pdf adındaki bir dosya yürütülebilir olabilir ve Content-Type: image/png yalancı olabilir.
Daha güçlü yöntem dosyanın ilk baytlarını incelemektir, buna genellikle “magic bytes” veya dosya imzası denir. Birçok formatın tutarlı başlıkları vardır (PNG ve JPEG gibi). Başlık izin verilenle eşleşmiyorsa reddedin.
Pratik bir doğrulama ayarı:
Yeniden adlandırma düşündüğünüzden daha önemlidir. Kullanıcı sağladığı adları doğrudan saklarsanız yol hileleri, garip karakterler ve kazara üzerine yazma riskini davet edersiniz. Depolama için üretilmiş bir kimlik kullanın ve orijinal dosya adını yalnızca görüntüleme için saklayın.
Profil fotoğrafları için yalnızca JPEG ve PNG kabul edin, başlıkları doğrulayın ve mümkünse meta verileri temizleyin. Belgeler için yalnızca PDF’ye izin vermeyi düşünün ve aktif içerik içerenleri reddedin. SVG veya HTML’e ihtiyaç duyarsanız, bunları potansiyel olarak yürütülebilir kabul edip izole edin.
Çoğu yükleme kesintisi “havalı hacker” taktiklerinden değil. Çok büyük dosyalar, çok fazla istek veya sunucuları meşgul eden yavaş bağlantılardır. Her baytı bir maliyet olarak değerlendirin.
Özellik başına maksimum boyut seçin, tek bir genel sayı seçmeyin. Bir avatar vergi belgesi veya kısa video ile aynı limiti gerektirmez. Normal görünen en küçük limiti belirleyin, sonra gerçekten ihtiyaç duyduğunuzda ayrı bir “büyük yükleme” akışı ekleyin.
Sınırları birden fazla yerde uygulayın çünkü istemciler yalan söyleyebilir: uygulama mantığı, web sunucusu veya ters proxy, yükleme zaman aşımı ve beyan edilen boyut çok büyükse tam gövdeyi okumadan erken reddetme gibi.
Somut örnek: avatarlar 2 MB ile sınırlandırılsın, PDF’ler 20 MB ile sınırlandırılsın, daha büyük olanlar farklı bir yol gerektirsin (ör. imzalı URL ile doğrudan obje depolamaya).
Küçük dosyalar bile bir döngüde birisi tarafından tekrar tekrar yüklenirse DoS olabilir. Yükleme uç noktalarına kullanıcı ve IP başına oran sınırlamaları ekleyin. Anonim trafik için giriş yapmış kullanıcılardan daha sıkı sınırlar düşünün.
Sürülebilir yüklemeler zayıf ağdaki gerçek kullanıcılara yardımcı olur, ancak oturum jetonu sıkı olmalıdır: kısa süreli, kullanıcıya bağlı ve belirli bir dosya boyutu ve hedefe bağlanmış. Aksi halde “resume” uç noktaları depolamaya ücretsiz bir hat olabilir.
Bir yüklemeyi engellediğinizde kullanıcıya açık hatalar döndürün (dosya çok büyük, çok fazla istek) ama iç ayrıntıları sızdırmayın (stack trace, bucket adları, satıcı detayları).
Güvenli yüklemeler sadece ne kabul ettiğinizle ilgili değildir. Dosyanın nereye gittiği ve daha sonra nasıl geri verildiği de önemlidir.
Yükleme baytlarını ana veritabanınızın dışına çıkarın. Çoğu uygulama veritabanında sadece metadata (sahip kullanıcı ID’si, orijinal dosya adı, tespit edilen tür, boyut, checksum, depolama anahtarı, oluşturulma zamanı) tutar. Baytları büyük bloblar için tasarlanmış obje depolamada veya dosya servisinde saklayın.
Public ve private dosyaları depolama düzeyinde ayırın. Farklı kurallara sahip farklı bucket veya konteynerler kullanın. Genel dosyalar (ör. herkese açık avatarlar) giriş yapmadan okunabilir olabilir. Özel dosyalar (sözleşmeler, faturalar, tıbbi belgeler) asla herkese açık olmamalı, URL tahmin edilse bile okunamaz olmalıdır.
Mümkünse kullanıcı dosyalarını uygulamanızın ana domaininden sunmaktan kaçının. Riskli bir dosya (HTML, script içeren SVG veya tarayıcı MIME sniffing tuhaflıkları) atlanırsa, ana domain üzerinde barındırmak hesap ele geçirmeye yol açabilir. Ayrı bir indirme domaini (veya depolama domaini) patlama alanını sınırlar.
İndirmelerde güvenli başlıkları zorlayın. Kullanıcının iddia ettiğine değil, izin verdiğiniz şeylere göre öngörülebilir bir Content-Type ayarlayın. Tarayıcı tarafından yorumlanabilecek her şey için indirme olarak gönderin.
Sürprizleri önleyen birkaç varsayılan:
Content-Disposition: attachment kullanın.Content-Type kullanın (veya application/octet-stream).Tutma (retention) da güvenliktir. Terk edilmiş yüklemeleri silin, değiştirildikten sonra eski sürümleri kaldırın ve geçici dosyalar için zaman sınırları belirleyin. Az veri saklamak, sızabilecek veri miktarını azaltır.
İmzalı URL’ler (pre-signed URLs) kullanıcılara depolama bucket’ını herkese açık yapmadan veya her baytı API’nizden geçirmek zorunda kalmadan yükleme/indirme izni vermenin yaygın yoludur. URL geçici izin taşır ve sonra süresi dolar.
İki yaygın akış:
Doğrudan-depolamaya yükleme API yükünü azaltır, ancak depolama kuralları ve URL kısıtlamaları daha önemli hale gelir.
İmzalı URL’yi bir tek kullanımlık anahtar gibi ele alın. Spesifik ve kısa ömürlü yapın.
Pratik bir desen: önce bir yükleme kaydı oluşturun (durum: pending), sonra imzalı URL’yi verin. Yüklemeden sonra nesnenin varlığını ve beklenen boyut/tür ile eşleştiğini doğrulamadan durumu hazır yapmayın.
Güvenli bir yükleme akışı esasen net kurallar ve net durumlar içerir. Her yüklemeyi kontrol edilene kadar güvensiz kabul edin.
Her özelliğin neleri kabul ettiğini yazın. Bir profil fotoğrafı ile vergi belgesi aynı dosya türleri, boyut sınırları veya görünürlük paylaşmamalıdır.
İzin verilen türleri ve özellik başına boyut limitini tanımlayın (ör. fotoğraflar 5 MB’e kadar; PDF’ler 20 MB’e kadar). Aynı kuralları backend’de uygulayın.
Baytlar gelmeden önce bir “yükleme kaydı” oluşturun. Saklayın: sahibi (kullanıcı veya organizasyon), amaç (avatar, fatura, eklenti), orijinal dosya adı, beklenen maksimum boyut ve pending gibi bir durum.
Özel bir konuma yükleyin. İstemcinin son yolu seçmesine izin vermeyin.
Sunucu tarafında tekrar doğrulayın: boyut, magic bytes/tür, allowlist. Geçerse durumu uploaded yapın.
Kötü amaçlı yazılım taraması yapın ve durumu clean veya quarantined olarak güncelleyin. Tarama asenkron ise erişimi beklerken kilitleyin.
Durum clean olduğunda indirme, önizleme veya işleme izin verin.
Küçük örnek: profil fotoğrafı için kullanıcıya bağlı bir avatar amacıyla bir kayıt oluşturun, özel olarak saklayın, gerçekten JPEG/PNG olduğunu (sadece isimle değil) doğrulayın, tarayın ve sonra bir önizleme URL’si oluşturun.
Kötü amaçlı yazılım taraması bir güvenlik ağıdır, garanti değil. Bilinen kötü dosyaları ve bariz numaraları yakalar ama her şeyi tespit edemez. Amaç basit: riski azaltmak ve bilinmeyen dosyaları varsayılan olarak zararsız hale getirmektir.
Güvenilir bir desen önce karantinadır. Yeni her yüklemeyi özel, halka açık olmayan bir konuma kaydedin ve onu bekleyen durumda işaretleyin. Kontroller geçildikten sonra “clean” konumuna taşıyın veya kullanılabilir olarak işaretleyin.
Senkron taramalar yalnızca küçük dosyalar ve düşük trafik için uygundur çünkü kullanıcı beklemek zorunda kalır. Çoğu uygulama asenkron tarar: yüklemeyi kabul eder, “işleniyor” durumunu döndürür, arka planda tarama yapar.
Temel tarama tipik olarak bir antivirüs motoru (veya hizmet) artı birkaç koruma katmanıdır: AV taraması, dosya türü kontrolleri (magic bytes), arşiv sınırları (zip bombaları, iç içe geçmiş zip’ler, büyük açılmamış boyutlar) ve ihtiyaç duyulmayan formatları engelleme.
Tarayıcı başarısız olursa, zaman aşımına uğrarsa veya “bilinmiyor” dönerse dosyayı şüpheli kabul edin. Karantinada tutun ve indirme bağlantısı vermeyin. Burada ekipler yanar: “tarama başarısız” asla “yine de yayınla” olmamalıdır.
Bir dosyayı engellediğinizde mesajı nötr tutun: “Bu dosyayı kabul edemedik. Farklı bir dosya deneyin veya destek ile iletişime geçin.” Malware tespit ettiğinizi iddia etmeyin, emin değilseniz.
Profil fotoğrafı (herkese açık gösterilen) ve PDF makbuz (özel, faturalama veya destek için) gibi iki özellik düşünün. Her ikisi de yükleme problemi ama aynı kuralları paylaşmamalı.
Profil fotoğrafı için katı olun: yalnızca JPEG/PNG izin verin, boyutu sınırlandırın (ör. 2–5 MB), sunucu tarafında yeniden kodlayın ki kullanıcı orijinal baytları doğrudan sunulmasın. Kontrollerden sonra kamuya açık depolamaya koyun.
PDF makbuzu için daha büyük boyuta izin verin (ör. 20 MB’e kadar), varsayılan olarak özel tutun ve ana uygulama domaininden inline olarak renderlamaktan kaçının.
Basit bir durum modeli kullanıcıyı içeriği açmadan bilgilendirir:
İmzalı URL’ler burada iyi uyum sağlar: yazma için kısa ömürlü imzalı URL kullanın (yazma sadece bir nesne anahtarına), okuma için ayrı kısa ömürlü imzalı URL verin ve yalnızca durum clean olduğunda verin.
Araştırma için ihtiyaç duyduğunuz bilgileri loglayın, dosyanın kendisini değil: kullanıcı ID, dosya ID, tür tahmini, boyut, depolama anahtarı, zaman damgaları, tarama sonucu, istek ID’leri. Ham içerik veya belgelerdeki hassas verileri loglamaktan kaçının.
Çoğu yükleme hatası küçük bir “geçici” kısayolun kalıcı hale gelmesiyle oluşur. Her dosyanın güvensiz olduğunu, her URL’nin paylaşılacağını ve her “sonra düzeltilir” ayarının unutulacağını varsayın.
Tekrarlayan tuzaklar:
Content-Type ile sunmak, tarayıcının riskli içeriği yorumlamasına izin vermek.Monitoring, ekiplerin atladığı tek şeydir ta ki depolama faturası patlayana kadar. Yükleme hacmini, ortalama boyutu, en çok yükleyenleri ve hata oranlarını takip edin. Bir ele geçirilmiş hesap gece boyunca sessizce binlerce büyük dosya yükleyebilir.
Örnek: bir takım avatarları kullanıcı tarafından verilen dosya adlarıyla “avatar.png” gibi paylaşılan bir klasörde saklar. Bir kullanıcı başkalarının resimlerini ezebilir. Düzeltme sıkıcı ama etkili: obje anahtarlarını sunucu tarafında üretin, yüklemeleri varsayılan olarak özel tutun ve yeniden boyutlandırılmış resmi kontrollü bir yanıtla sunun.
Bunu göndermeden önce son kontrol olarak kullanın. Her öğeyi bir sürüm engelleyici olarak ele alın; çünkü çoğu olay bir eksik koruyucudan gelir.
Content-Type, güvenli dosya adları ve belgeler için attachment.Kurallarınızı açık ve sade bir dille yazın: izin verilen türler, maksimum boyutlar, kim neye erişebilir, imzalı URL’lerin ne kadar yaşadığı ve “tarama geçti”nin ne anlama geldiği. Bu, ürün, mühendislik ve destek arasında paylaşılan bir sözleşme olur.
Aşırı büyük dosyalar, yeniden adlandırılmış yürütülebilirler, yetkisiz okumalar, süresi dolmuş imzalı URL’ler ve “tarama bekleniyor” indirmelerini yakalayacak birkaç test ekleyin. Bu testler bir olayla karşılaştırıldığında ucuzdur.
Hızla inşa edip yineleyen ekipler için, değişiklikleri planlayıp güvenle geri alabileceğiniz bir iş akışı kullanmak yardımcı olur. Ekipler Koder.ai (koder.ai) kullanırken genellikle planlama modu ve snapshot/rollback ile yükleme kurallarını sıkılaştırırken bu tür iş akışlarına dayanır, ancak temel gereksinim değişmez: politika backend tarafından uygulanır, UI değil.
Start with private by default and treat every upload as untrusted input. Enforce four basics server-side:
If you can answer those clearly, you’re already ahead of most incidents.
Because users can upload a “mystery box” that your app stores and might later serve back to other users. That can lead to:
It’s rarely just “someone uploaded a virus.”
Storing is keeping bytes somewhere. Serving is delivering those bytes to browsers and apps.
The danger is when your app serves user uploads with the same trust and rules as your main site. If a risky file is treated like a normal page, the browser may execute it (or users may trust it too much).
A safer default is: store privately, then serve through controlled download responses with safe headers.
Use default deny and check access every time a file is downloaded or previewed.
Practical rules:
Don’t trust the filename extension or the browser’s Content-Type. Validate on the server:
Because outages often come from boring abuse: too many uploads, huge files, or slow connections tying up server resources.
Defaults that work well:
Treat every byte as cost and every request as potential abuse.
Yes, but do it carefully. Signed URLs let the browser upload/download directly from object storage without making the bucket public.
Good defaults:
Direct-to-storage reduces API load, but it makes scoping and expiration non-negotiable.
The safest pattern is:
pendingScanning helps, but it’s not a guarantee. Use it as a safety net, not your only control.
Practical approach:
The key is policy: “not scanned” should never mean “available.”
Serve files in a way that prevents browsers from interpreting them as web pages.
Good defaults:
Content-Disposition: attachment for documents/uploads/ is fine”Most real bugs are simple “I can see another user’s file” mistakes.
If the bytes don’t match an allowed format, reject the upload.
cleanquarantinedcleanThis prevents “scan failed” or “still processing” files from being shared accidentally.
Content-Type (or application/octet-stream)This reduces the risk that an uploaded file turns into a phishing page or script execution.