Komutları üretim sunucusunda çalıştırmadan önce yedek alın ve mümkünse bir test ortamında deneyin. Yönetim erişiminizi (SSH, panel) kaybetmemek için ayar değişikliklerinde açık bir ikinci oturum bulundurun.
Bu rehberde ne yapacağız?
Dosya yükleme özelliği, web sitelerindeki en tehlikeli işlevlerden biridir. Kötü yapılandırılmış bir yükleme formu, saldırganın sunucuya bir PHP kabuk betiği yerleştirip çalıştırmasına yol açabilir. Bu rehberde dosya yükleme güvenliğini dört ilke üzerinden kuracağız: uzantı denetimi, gerçek MIME türü denetimi, güvenli depolama ve yüklenen dizinde çalıştırmanın engellenmesi.
Rehber, PHP ile site barındıran sunucu yöneticileri ve geliştiriciler içindir. Tahmini süre: 40-60 dakika.
Gereksinimler
- PHP (fileinfo eklentisi etkin) ve Nginx veya Apache web sunucusu
- Web sunucusu yapılandırmasını değiştirme yetkisi (sudo)
- Yüklenen dosyaların yazılacağı, web kökünün dışında bir dizin
- Test için bir geliştirme veya hazırlık ortamı
Adım adım dosya yükleme güvenliği
1. Tehdidi anlayın
Saldırgan genellikle bir PHP betiğini resim gibi göstermeye çalışır: kabuk.php, kabuk.php.jpg, kabuk.phtml, içeriği sahte bir görsel başlığıyla başlayan betik veya çift uzantılı dosyalar. Tek bir denetime güvenmeyin; birden çok katmanı birlikte uygulayın.
2. PHP yükleme sınırlarını ayarlayın
Boyut sınırlarını gerçek ihtiyaca göre daraltın ve geçici dizini kontrol edin. Sınırlar saldırganın disk ve bellek tüketmesini zorlaştırır.
file_uploads = On
upload_max_filesize = 5M
post_max_size = 8M
max_file_uploads = 5Bu ayarlar php.ini veya PHP-FPM havuz yapılandırmasında yer alır; dosya konumu dağıtıma göre değişir. Sınırları değiştirdikten sonra PHP-FPM veya web sunucusunu yeniden yükleyin. Web sunucusunun kendi istek boyutu sınırı da (Nginx için client_max_body_size) uyumlu olmalıdır.
3. Uzantıyı beyaz liste ile denetleyin
İstemcinin bildirdiği dosya adına güvenmeyin. İzin verilen uzantıları sabit bir listeyle eşleştirin; kara liste yaklaşımı kaçınılmaz olarak bir şey kaçırır.
<?php
$izinliUzantilar = ['jpg', 'jpeg', 'png', 'gif', 'pdf'];
$uzanti = strtolower(pathinfo($_FILES['dosya']['name'], PATHINFO_EXTENSION));
if (!in_array($uzanti, $izinliUzantilar, true)) {
http_response_code(400);
exit('Bu dosya türü kabul edilmiyor');
}4. Gerçek MIME türünü içerikten belirleyin
Tarayıcının gönderdiği Content-Type başlığı kolayca sahtelenebilir. PHP'nin finfo sınıfı dosyanın gerçek içeriğine bakarak türü belirler. Uzantı ile MIME türünün tutarlı olduğunu denetleyin.
<?php
$izinliMime = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'pdf' => 'application/pdf',
];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['dosya']['tmp_name']);
if (($izinliMime[$uzanti] ?? null) !== $mime) {
http_response_code(400);
exit('Dosya içeriği uzantıyla uyuşmuyor');
}5. Görselleri yeniden kodlayın
Görsel yüklemelerinde dosyayı GD ile yeniden işleyip kaydetmek, içine gömülmüş zararlı veriyi (örneğin meta veriler içindeki betik) temizler. Yeniden kodlama başarısız olursa dosyayı reddedin. Bu kod yalnızca görsel uzantılarında çalışmalıdır; $hedefYol 6. adımda üretilir ve çıktı JPEG olduğundan uzantıyı jpg yapın (şeffaflık için imagepng kullanın). Görseli böyle yazdığınızda 6. adımdaki move_uploaded_file çağrısını atlayın.
<?php
$kaynak = @imagecreatefromstring(file_get_contents($_FILES['dosya']['tmp_name']));
if ($kaynak === false) {
http_response_code(400);
exit('Geçerli bir görsel değil');
}
imagejpeg($kaynak, $hedefYol, 85);
imagedestroy($kaynak);6. Dosyayı rastgele adla ve web kökü dışında saklayın
Kullanıcının verdiği adı kullanmayın; yol gezinmesi (../) ve ad çakışmasını önlemek için rastgele bir ad üretin. Dizini web kökünün dışında tutarsanız dosyalar doğrudan URL ile erişilebilir olmaz; gerektiğinde bir PHP betiği yetki denetimi yaparak sunar.
<?php
$yeniAd = bin2hex(random_bytes(16)) . '.' . $uzanti;
$hedefYol = '/var/uygulama-yuklemeler/' . $yeniAd;
if (!move_uploaded_file($_FILES['dosya']['tmp_name'], $hedefYol)) {
http_response_code(500);
exit('Kaydedilemedi');
}
chmod($hedefYol, 0640);Dizini hazırlayın ve sahipliğini web sunucusu kullanıcısına verin. Kullanıcı adı dağıtıma göre değişir (Debian/Ubuntu'da www-data, RHEL tabanlı sistemlerde Apache için apache, Nginx için genellikle nginx); PHP-FPM havuzu farklı bir kullanıcıyla çalışıyorsa o kullanıcıyı verin.
sudo install -d -m 750 -o www-data -g www-data /var/uygulama-yuklemeler7. Yükleme dizininde betik çalıştırmayı engelleyin
Dosyaların web kökü içinde servis edilmesi gerekiyorsa o dizinde PHP ve benzeri betiklerin çalışmasını kesin olarak kapatın. Bu, tüm denetimler atlatılsa bile son savunma hattıdır.
Nginx için:
location ^~ /yuklemeler/ {
location ~* \.(php|phtml|phar|pl|py|cgi|sh)$ { return 403; }
add_header X-Content-Type-Options "nosniff" always;
}Apache için yükleme dizinine .htaccess (AllowOverride etkinse) veya sanal sunucu yapılandırmasına şunu ekleyin:
<FilesMatch "\.(php|phtml|phar|pl|py|cgi|sh)$">
Require all denied
</FilesMatch>Yapılandırmayı denetleyip yeniden yükleyin: sudo nginx -t && sudo systemctl reload nginx veya sudo apachectl configtest && sudo systemctl reload apache2 (RHEL'de httpd).
8. Virüs taraması ve kota ekleyin
Kullanıcılar arasında paylaşılan dosyalar için ClamAV gibi bir tarayıcıyı yükleme sonrası kuyrukta çalıştırın ve kullanıcı başına kota koyun. Böylece zararlı yazılım yayılımı ve disk doldurma saldırıları sınırlanır.
sudo apt install -y clamav clamav-freshclam # Debian / Ubuntu
sudo dnf install -y clamav clamav-update # RHEL tabanlı (EPEL gerekir)
sudo clamscan -r -i /var/uygulama-yuklemelerİlk taramadan önce imza veritabanının freshclam ile güncellendiğinden emin olun; Debian/Ubuntu'da bunu clamav-freshclam hizmeti otomatik yapar.
Doğrulama
Güvenlik önlemlerinin gerçekten çalıştığını bir saldırı simülasyonuyla kanıtlayın; yalnızca kendi test ortamınızda deneyin.
echo '<?php echo 1+1;' > /tmp/test.php
curl -s -F 'dosya=@/tmp/test.php' http://test.example.local/yukle.php -o /dev/null -w '%{http_code}\n'
cp /tmp/test.php /tmp/test.jpg
curl -s -F 'dosya=@/tmp/test.jpg;type=image/jpeg' http://test.example.local/yukle.php -o /dev/null -w '%{http_code}\n'Beklenen sonuç: her iki istek de 400 benzeri bir hata ile reddedilmelidir (ilki uzantı, ikincisi içerik denetimi nedeniyle). Ardından web kökü içindeki yükleme dizinine (7. adımdaki /yuklemeler/) elle bir .php dosyası koyup erişmeyi deneyin; sunucu 403 dönmeli ve betik çalışmamalıdır.
curl -s -o /dev/null -w '%{http_code}\n' http://test.example.local/yuklemeler/deneme.phpSık karşılaşılan sorunlar
- Büyük dosya yüklenmiyor:
upload_max_filesize,post_max_sizeve Nginxclient_max_body_sizedeğerlerinin tutarlı olduğunu kontrol edin. - Geçerli görsel reddediliyor: fileinfo eklentisinin etkin olduğundan ve MIME eşlemesinin doğru olduğundan emin olun; bazı görsel biçimleri (örneğin webp) listeye eklenmemiş olabilir.
- Yükleme sonrası dosya görünmüyor: dizin sahipliği ve izinleri web sunucusu kullanıcısına yazma izni vermiyor olabilir; günlüğe bakın.
- .htaccess etki etmiyor: Apache'de
AllowOverridekapalıdır; kuralı sanal sunucu yapılandırmasına taşıyın. - SELinux engelliyor: RHEL tabanlı sistemlerde yazma dizini için doğru bağlam gerekir; kapatmak yerine
semanage fcontextile uygun bağlamı verin.
Güvenlik ve bakım notları
- Yüklenen dosyalara hiçbir zaman yürütme izni vermeyin; dosya izinlerini
0640veya daha kısıtlı tutun. - SVG ve HTML dosyaları betik içerebilir; izin vermiyorsanız listeden çıkarın, veriyorsanız ayrı bir alan adından ve
Content-Disposition: attachmentile sunun. - Yükleme yetkisini yalnızca oturum açmış ve yetkili kullanıcılara verin.
- Yükleme dizinini düzenli tarayın ve beklenmeyen uzantıları arayın.
- Yüklenen dosya yollarını veritabanında tutun, kullanıcı girdisinden dosya yolu üretmeyin.
- Dosya bütünlüğü izleme ve günlük incelemesi ile yeni eklenen şüpheli dosyaları erken yakalayın.
Sık sorulan sorular
Yalnızca MIME türünü kontrol etmek yeterli mi?
Hayır. MIME denetimi, uzantı beyaz listesi, yeniden kodlama, güvenli depolama ve çalıştırma engeli birlikte uygulanmalıdır. Tek bir denetim atlatıldığında diğerleri zararı sınırlar.
Dosyaları neden web kökü dışında saklamalıyım?
Web kökü dışındaki dosyalara URL ile doğrudan erişilemez ve çalıştırılamaz. İçeriği yetki denetimi yapan bir betik üzerinden sunarsanız hem gizlilik hem güvenlik sağlanır.
Görsel yeniden kodlama her zaman gerekli mi?
Zorunlu değildir ama güçlü bir ek korumadır. Özellikle herkese açık yükleme formlarında görseller işlenerek içlerine gömülmüş beklenmeyen veriler temizlenir.
İlgili sözlük terimleri
Bu kategorideki diğer rehberler
- AIDE ile dosya bütünlüğü izlemeAIDE ile sunucudaki dosya değişikliklerini izleyin: kurulum, veritabanı oluşturma, kural yazma, düzenli denetim, güncelleme ve sonuçları güvenle yorumlama adımları.
- XSS önleme ve CSP ile site savunmasıSiteler arası betik çalıştırma (XSS) saldırılarını çıktı kaçışı, güvenli çerezler ve Content-Security-Policy başlığıyla önleyin; Nginx ve Apache örnekleri ve Report-Only ile güvenli geçiş.
- SQL enjeksiyonunu önleme: hazır sorgular (prepared statements) ve doğrulama (PHP PDO örnekleriyle)SQL enjeksiyonunu PHP PDO hazır sorgularıyla önleyin: parametre bağlama, beyaz liste ile ORDER BY, IN listeleri, en az yetkili veritabanı kullanıcısı ve saldırı denemesiyle doğrulama.
- ClamAV ile sunucuda zararlı yazılım taramasıClamAV kurulumu, imza güncelleme, clamscan ile site dizinlerini tarama, cron ile otomatik rapor ve karantina adımlarını Debian ile AlmaLinux için öğrenin.