Tehdit Modeli ve Doğrulama Sırası
Dosya yükleme güvenliği yalnızca uzantı kontrolüne dayanmaz. Kullanıcı, dosya adını ve istemcinin gönderdiği MIME bilgisini değiştirebilir. Bu nedenle doğrulama sunucu tarafında, birden fazla bağımsız kontrolle yapılmalıdır.
Önerilen akış:
PHP ile Güvenli Saklama Örneği
Aşağıdaki fonksiyon, yalnızca JPEG ve PNG kabul eder, dosya türünü sunucu tarafında tespit eder ve dosyayı uygulama tarafından yapılandırılmış depolama dizinine taşır. $storageDir sabit veya güvenilir yapılandırmadan gelmeli; kullanıcı girdisiyle oluşturulmamalıdır.
Örnekte döndürülen rastgele kimlik veritabanında saklanabilir. İstemciye gerçek dosya sistemi yolunu vermek yerine, yetkili bir indirme endpoint'i kullanın. Bu endpoint dosyanın sahibini ve erişim kapsamını kontrol etmeli, dosyayı güvenilir bir kayıttan bulmalı ve kullanıcıdan gelen adı doğrulamadan Content-Disposition başlığına yazmamalıdır. Görüntüleri tarayıcıda açmak yerine indirmeye zorlamak, uygulamanın tehdit modeline göre daha güvenli olabilir.
PHP ve Web Sunucusu Yapılandırması
Uygulama kodu doğru olsa bile çalışma ortamı ek risk oluşturabilir. Aşağıdaki ayarları dağıtım ortamında ayrıca doğrulayın:
Test Matrisi ve Hata Yönetimi
Testler yalnızca başarılı yüklemeyi değil, sınır ve saldırı senaryolarını da kapsamalıdır. Dosya sistemi, MIME tespiti ve rastgele kimlik üretimi soyutlanırsa birim testleri daha hızlı ve deterministik olur.
Dosya yükleme güvenliği yalnızca uzantı kontrolüne dayanmaz. Kullanıcı, dosya adını ve istemcinin gönderdiği MIME bilgisini değiştirebilir. Bu nedenle doğrulama sunucu tarafında, birden fazla bağımsız kontrolle yapılmalıdır.
Önerilen akış:
- Yükleme hatasını, geçici dosyanın varlığını ve boyut sınırını kontrol edin.
- İzin verilen uzantıları dar bir allowlist ile tanımlayın. Uzantıyı yalnızca sınıflandırma amacıyla kullanın; güvenlik kararını tek başına buna dayandırmayın.
- Gerçek türü finfo ile tespit edin. İstemcinin gönderdiği Content-Type başlığına güvenmeyin.
- Gönderilen uzantı ile tespit edilen MIME türünün uyumlu olup olmadığını denetleyin.
- Kullanıcıdan gelen dosya adını saklama adı olarak kullanmayın; rastgele ve çakışmayacak bir kimlik üretin.
- Dosyayı mümkünse web kökü dışında, yalnızca uygulamanın erişebildiği bir dizinde tutun.
- Kimlik doğrulama, sahiplik, rol ve dosya içeriğine göre yetkilendirme uygulayın.
PHP ile Güvenli Saklama Örneği
Aşağıdaki fonksiyon, yalnızca JPEG ve PNG kabul eder, dosya türünü sunucu tarafında tespit eder ve dosyayı uygulama tarafından yapılandırılmış depolama dizinine taşır. $storageDir sabit veya güvenilir yapılandırmadan gelmeli; kullanıcı girdisiyle oluşturulmamalıdır.
function storeImage(array $file, string $storageDir): string
{
if (!isset($file['error'], $file['tmp_name'], $file['size'], $file['name'])) {
throw new RuntimeException('Geçersiz yükleme verisi.');
}
if ($file['error'] !== UPLOAD_ERR_OK || !is_uploaded_file($file['tmp_name'])) {
throw new RuntimeException('Dosya yüklenemedi.');
}
$maxBytes = 5 * 1024 * 1024;
if (!is_int($file['size']) && !ctype_digit((string) $file['size'])) {
throw new RuntimeException('Geçersiz dosya boyutu.');
}
$size = (int) $file['size'];
if ($size < 1 || $size > $maxBytes) {
throw new RuntimeException('Dosya boyutu izin verilen aralıkta değil.');
}
$allowed = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
];
$extension = strtolower((string) pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, ['jpg', 'jpeg', 'png'], true)) {
throw new RuntimeException('Desteklenmeyen dosya uzantısı.');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
if (!isset($allowed[$mime])) {
throw new RuntimeException('Desteklenmeyen dosya türü.');
}
$normalizedExtension = $extension === 'jpeg' ? 'jpg' : $extension;
if ($normalizedExtension !== $allowed[$mime]) {
throw new RuntimeException('Dosya uzantısı ve içeriği uyuşmuyor.');
}
if (!is_dir($storageDir) && !mkdir($storageDir, 0750, true)) {
throw new RuntimeException('Depolama dizini oluşturulamadı.');
}
$name = bin2hex(random_bytes(16)) . '.' . $allowed[$mime];
$target = rtrim($storageDir, DIRECTORY_SEPARATOR)
. DIRECTORY_SEPARATOR . $name;
if (!move_uploaded_file($file['tmp_name'], $target)) {
throw new RuntimeException('Dosya güvenli konuma taşınamadı.');
}
return $name;
}
Örnekte döndürülen rastgele kimlik veritabanında saklanabilir. İstemciye gerçek dosya sistemi yolunu vermek yerine, yetkili bir indirme endpoint'i kullanın. Bu endpoint dosyanın sahibini ve erişim kapsamını kontrol etmeli, dosyayı güvenilir bir kayıttan bulmalı ve kullanıcıdan gelen adı doğrulamadan Content-Disposition başlığına yazmamalıdır. Görüntüleri tarayıcıda açmak yerine indirmeye zorlamak, uygulamanın tehdit modeline göre daha güvenli olabilir.
PHP ve Web Sunucusu Yapılandırması
Uygulama kodu doğru olsa bile çalışma ortamı ek risk oluşturabilir. Aşağıdaki ayarları dağıtım ortamında ayrıca doğrulayın:
- upload_max_filesize ve post_max_size, gerçek ihtiyacın makul bir üst sınırıyla yapılandırılmalıdır.
- Depolama dizini web kökünün dışında tutulmalıdır.
- Web kökü kullanılmak zorundaysa PHP, CGI veya başka yürütülebilir içeriklerin çalıştırılması engellenmelidir.
- Dizin ve dosya izinleri en az ayrıcalık ilkesine göre verilmelidir.
- Disk doluluğu, başarısız yüklemeler, olağan dışı boyutlar ve reddedilen türler izlenmelidir.
- Rate limit, CSRF koruması, kimlik doğrulama ve rol tabanlı yetkilendirme uygulanmalıdır.
Test Matrisi ve Hata Yönetimi
Testler yalnızca başarılı yüklemeyi değil, sınır ve saldırı senaryolarını da kapsamalıdır. Dosya sistemi, MIME tespiti ve rastgele kimlik üretimi soyutlanırsa birim testleri daha hızlı ve deterministik olur.
- Geçerli JPEG ve PNG dosyaları kabul ediliyor mu?
- İzin verilmeyen uzantı, sahte MIME başlığı veya uzantı-içerik uyuşmazlığı reddediliyor mu?
- Tam boyut sınırındaki ve sınırı aşan dosyalar doğru işleniyor mu?
- Eksik alan, bozuk geçici dosya ve başarısız taşıma güvenli hataya dönüşüyor mu?
- Aynı içerik tekrar yüklendiğinde dosya adları çakışıyor mu?
- Dosya adı; yol geçişi, null byte veya özel karakter içerse bile güvenli biçimde etkisizleştiriliyor mu?
- Yetkisiz kullanıcı başka bir kullanıcının dosyasını indirebiliyor mu?
- Disk dolu olduğunda geçici veya yarım kalmış dosyalar temizleniyor mu?