- Konu sahibi
- #1
Dosya adı çakışmasını önlemenin en güvenilir yolu, kullanıcının gönderdiği adı depolama adı olarak kullanmamaktır. Yüklenen dosyayı sunucu tarafında rastgele veya UUID tabanlı benzersiz bir adla kaydedin; özgün adı ise veritabanında gösterim amacıyla ayrı tutun. Kaydetme işlemi de mevcut dosyanın üzerine yazmayı reddeden bir yöntemle yapılmalıdır.
İki kullanıcı
Bu nedenle üç bilgiyi ayırın:
Uzantıyı yalnızca kullanıcı adından kopyalamayın. İzin verilen türleri uygulamanızın gereksinimine göre sınırlayın ve dosyanın gerçek içerik türünü sunucu tarafında kontrol edin. Özgün adı doğrudan yolun parçası yapmak; yol geçişi, özel karakter ve aynı ada sahip dosya sorunlarını birlikte doğurabilir.
Aşağıdaki işlev yalnızca çakışmasız depolama adımını gösterir; tam bir HTTP yükleme uç noktası değildir. Çağrıdan önce oturum/yetki ve gerekiyorsa CSRF, UPLOAD_ERR_OK, skaler alan yapısı, boyut ve izin verilen içerik türü uygulamada doğrulanmış olmalıdır. Hedef dizini önceden, web kökü dışında ve yalnız uygulamanın yazabildiği özel bir alan olarak hazırlayın. İşlev, PHP’nin yüklediği geçici dosyayı benzersiz bir ada kopyalar. Hedef dosya,
Doğrulamalar tamamlandıktan sonra işlevi geçerli geçici yükleme yolu ve hazırladığınız özel dizinle çağırın. Dönen değer depolama adıdır; veritabanındaki dosya kaydına bunu ve doğrulanmış içerik türünü yazın. Aynı özgün adla iki yükleme farklı depolama adları üretmelidir. İşlev mevcut bir hedefi açmaz; rastgele çakışma durumunda başka aday dener. Tekrarlanan açma başarısızlığı izin, kota veya disk problemi de olabilir; bunu her zaman rastgele çakışma sanmayın.
Örnekte .bin uzantısı kullanılması dosyayı güvenli ilan etmez. İndirme, yetki kontrolü yapan uç noktadan ve doğrulanmış içerik türüyle sunulmalı; özgün ad çıktı bağlamına uygun kaçışlanmalıdır. Kopyalanan bayt sayısı ve tampon yazımı kontrol edilir; hata durumunda yalnız o denemede oluşturulan yarım hedef kaldırılır. Üretimde beklenmeyen süreç kesintileri için dosya/veritabanı tutarlılığını ayrıca izleyin.
Dosya sisteminde güvenli bir depolama adı üretmek tek başına yeterli değildir. Dosyanın veritabanı kaydı da tutarlı olmalı ve aynı iş kaydı için aynı dosyanın iki kez oluşturulmaması gerekiyorsa ilgili iş anahtarına benzersizlik kuralı eklenmelidir.
Bu, çakışma önleme değil, tekilleştirme kararıdır. İçeriğin özetini sunucu tarafında hesaplayıp aynı özet için tek fiziksel dosya tutabilirsiniz; ancak silme ve erişim kayıtlarının birden fazla kaydı destekleyecek şekilde tasarlanması gerekir.
Genellikle gösterilebilir; fakat HTML çıktısında uygun şekilde kaçışlayın ve bu adı dosya yolu, komut veya sorgu parçası olarak kullanmayın. Depolama adı her durumda ayrı kalmalıdır.
Çakışma nasıl oluşur?
İki kullanıcı
logo.png yüklediğinde uygulama dosyayı doğrudan /uploads/logo.png konumuna yazarsa ikinci yükleme birincinin üzerine yazabilir. Yalnızca “dosya mevcutsa sonuna sayı ekle” yaklaşımı da eşzamanlı isteklerde güvenilir değildir: iki istek aynı boş adı aynı anda kontrol edip aynı sonucu seçebilir.Bu nedenle üç bilgiyi ayırın:
- Özgün ad: Kullanıcıya gösterilecek
logo.pnggibi ad. - Depolama adı: Sunucuda kullanılacak benzersiz ad, örneğin
a4f...c91.bin. - Kayıt kimliği: Dosyanın kullanıcıya, içeriğe veya ilgili kayda ait bağlantısı.
Uzantıyı yalnızca kullanıcı adından kopyalamayın. İzin verilen türleri uygulamanızın gereksinimine göre sınırlayın ve dosyanın gerçek içerik türünü sunucu tarafında kontrol edin. Özgün adı doğrudan yolun parçası yapmak; yol geçişi, özel karakter ve aynı ada sahip dosya sorunlarını birlikte doğurabilir.
Uygulanabilir örnek: PHP ile üzerine yazmadan kaydetme
Aşağıdaki işlev yalnızca çakışmasız depolama adımını gösterir; tam bir HTTP yükleme uç noktası değildir. Çağrıdan önce oturum/yetki ve gerekiyorsa CSRF, UPLOAD_ERR_OK, skaler alan yapısı, boyut ve izin verilen içerik türü uygulamada doğrulanmış olmalıdır. Hedef dizini önceden, web kökü dışında ve yalnız uygulamanın yazabildiği özel bir alan olarak hazırlayın. İşlev, PHP’nin yüklediği geçici dosyayı benzersiz bir ada kopyalar. Hedef dosya,
fopen(..., 'x') sayesinde zaten varsa açılmaz; böylece mevcut dosyanın üzerine yazılmaz. Özgün dosya adı depolama yolunda kullanılmaz.
PHP:
<?php
function saveUploadedWithoutOverwrite(string $tmp, string $directory): string
{
if (!is_uploaded_file($tmp) || !is_dir($directory)
|| !is_writable($directory)) {
throw new RuntimeException('Kaynak veya hedef uygun değil');
}
$source = fopen($tmp, 'rb');
if ($source === false) {
throw new RuntimeException('Geçici dosya açılamadı');
}
try {
$stat = fstat($source);
if ($stat === false) {
throw new RuntimeException('Dosya boyutu okunamadı');
}
for ($attempt = 0; $attempt < 5; $attempt++) {
$name = bin2hex(random_bytes(16)) . '.bin';
$path = $directory . DIRECTORY_SEPARATOR . $name;
$target = @fopen($path, 'x+b');
if ($target === false) {
continue;
}
$copied = stream_copy_to_stream($source, $target);
$ok = $copied !== false && $copied === $stat['size']
&& fflush($target);
$closed = fclose($target);
if (!$ok || !$closed) {
// Yalnız bu denemede oluşturduğumuz yarım dosya.
unlink($path);
throw new RuntimeException('Dosyanın tamamı yazılamadı');
}
return $name;
}
throw new RuntimeException('Hedef dosya ayrıcalıklı oluşturulamadı');
} finally {
fclose($source);
}
}
Doğrulamalar tamamlandıktan sonra işlevi geçerli geçici yükleme yolu ve hazırladığınız özel dizinle çağırın. Dönen değer depolama adıdır; veritabanındaki dosya kaydına bunu ve doğrulanmış içerik türünü yazın. Aynı özgün adla iki yükleme farklı depolama adları üretmelidir. İşlev mevcut bir hedefi açmaz; rastgele çakışma durumunda başka aday dener. Tekrarlanan açma başarısızlığı izin, kota veya disk problemi de olabilir; bunu her zaman rastgele çakışma sanmayın.
Örnekte .bin uzantısı kullanılması dosyayı güvenli ilan etmez. İndirme, yetki kontrolü yapan uç noktadan ve doğrulanmış içerik türüyle sunulmalı; özgün ad çıktı bağlamına uygun kaçışlanmalıdır. Kopyalanan bayt sayısı ve tampon yazımı kontrol edilir; hata durumunda yalnız o denemede oluşturulan yarım hedef kaldırılır. Üretimde beklenmeyen süreç kesintileri için dosya/veritabanı tutarlılığını ayrıca izleyin.
Hangi yöntemi ne zaman seçmeli?
Dosya sisteminde güvenli bir depolama adı üretmek tek başına yeterli değildir. Dosyanın veritabanı kaydı da tutarlı olmalı ve aynı iş kaydı için aynı dosyanın iki kez oluşturulmaması gerekiyorsa ilgili iş anahtarına benzersizlik kuralı eklenmelidir.
| Yöntem | Uygun kullanım | Dikkat edilmesi gereken |
|---|---|---|
| Rastgele ad | Genel dosya yüklemeleri | Özgün ad ayrıca saklanmalı |
| UUID | Kayıt kimliğiyle ilişkilendirilen dosyalar | UUID üretimi sunucu tarafında yapılmalı |
| İsim sonuna sayı ekleme | Yalnızca kullanıcı arayüzünde görünen adlar | Eşzamanlı kaydetmede tek başına güvenli değil |
Sık yapılan hatalar
- Dosya adını
basename()ile temizleyip yine de doğrudan kaydetmek: Bu, çakışmayı çözmez; yalnızca yolu kısmen düzenler. - Önce
file_exists()kontrol edip sonra yazmak: Kontrol ile yazma arasında başka bir istek dosyayı oluşturabilir. Hedefi özel açma veya veritabanı benzersizlik kuralı kullanın. - Özgün adı veritabanında benzersiz sanmak: İki farklı kullanıcı aynı adı seçebilir; özgün ad gösterim bilgisidir.
- Başarısız kopyadan sonra yarım dosya bırakmak: Hata durumunda hedefi silin ve veritabanı kaydını ancak dosya başarıyla yazıldıktan sonra oluşturun.
- Kullanıcıya gösterilen adı URL yolu yapmak: Depolama adını, indirme yetkilendirmesinden ve gösterim adından ayrı tutun.
Kaynaklar
- PHP: x modu ile ayrıcalıklı dosya oluşturma
- PHP: Geçerli yükleme kaynağını doğrulama
- OWASP: Dosya yükleme kontrolleri
Sık sorulan sorular
Aynı dosya tekrar yüklenirse içeriği karşılaştırıp tek kopya mı tutulmalı?
Bu, çakışma önleme değil, tekilleştirme kararıdır. İçeriğin özetini sunucu tarafında hesaplayıp aynı özet için tek fiziksel dosya tutabilirsiniz; ancak silme ve erişim kayıtlarının birden fazla kaydı destekleyecek şekilde tasarlanması gerekir.
Özgün dosya adını kullanıcıya göstermek güvenli mi?
Genellikle gösterilebilir; fakat HTML çıktısında uygun şekilde kaçışlayın ve bu adı dosya yolu, komut veya sorgu parçası olarak kullanmayın. Depolama adı her durumda ayrı kalmalıdır.