Dosya yükleme güvenliği ve web shell
Dosya yükleme özelliği (profil fotoğrafı, ürün görseli, özgeçmiş, ek belge) bir web sitesinin en riskli noktalarından biridir. Yükleme yeterince denetlenmezse sunucuya, tarayıcıdan çağrıldığında çalışan bir betik dosyası yerleştirilebilir. Bu tür dosyalara web shell denir: saldırgana sunucuda dosya okuma, değiştirme ve komut çalıştırma imkânı veren bir arka kapıdır. Web shell yalnızca yükleme formundan değil, ele geçirilmiş bir FTP hesabı ya da güncellenmemiş bir eklentideki açık üzerinden de yerleştirilebilir.
Savunmanın iki ayağı vardır: yüklemeyi sıkı kurallarla kabul etmek ve yüklenen dosyaların durduğu klasörde hiçbir betiğin çalışmamasını sağlamak. İkincisi, birincide hata yapılsa bile zararı önler.
Güvenli yüklemenin kuralları
-
Türü içerikten doğrulayın, izin listesi kullanın
Tarayıcının bildirdiği dosya türüne (
$_FILES[...]['type']) ve dosya adındaki uzantıya güvenmeyin; ikisi de kullanıcının denetimindedir. Dosyanın gerçek türünü sunucuda, içeriğinden okuyun (finfo) ve yalnızca izin verdiğiniz türleri kabul edin. "Şu uzantılar yasak" yaklaşımı (kara liste) güvenilir değildir; izin verilenleri sayın, gerisini reddedin.
-
Dosya adını siz belirleyin
Kullanıcının verdiği dosya adını diskte kullanmayın. Adın içindeki özel karakterler, yol ayırıcılar ve çift uzantılar (
belge.php.jpggibi) sorun kaynağıdır. Dosyayı sunucuda ürettiğiniz rastgele bir adla ve doğruladığınız türe karşılık gelen uzantıyla kaydedin; özgün adı yalnızca göstermek için veritabanında saklayın.
-
Dosyaları yürütmenin kapalı olduğu yerde saklayın
En güvenli yer web kökünün dışındaki bir klasördür: dosyaya adresle doğrudan ulaşılamaz, yalnızca yetki kontrolü yapan bir betik üzerinden sunulur. Dosyaların doğrudan adresle sunulması gerekiyorsa (ör. ürün görselleri), yükleme klasöründe betik çalıştırılmasını sunucu yapılandırmasıyla kapatın.
-
Boyutu, sayıyı ve kimin yükleyebildiğini sınırlayın
Dosya boyutuna ve bir kullanıcının yükleyebileceği dosya sayısına sınır koyun; aksi hâlde disk doldurulabilir. Yükleme mümkünse yalnızca oturum açmış kullanıcılara açık olsun ve form CSRF belirteciyle korunsun.
-
Web shell belirtilerini tanıyın
Önlemlere rağmen sunucuya zararlı bir dosya yerleşmiş olabilir. Beklenmeyen PHP dosyaları, değişen dosya tarihleri ve garip yönlendirmeler ilk işaretlerdir; ayrıntılı liste aşağıdaki "Web shell belirtileri" bölümündedir.
Güvenli yükleme örneği (PHP)
Aşağıdaki örnek tek bir görsel ya da PDF yüklemesini işler: yükleme hatasını ve boyutu denetler, türü içerikten okur, izin listesine bakar, rastgele ad üretir ve dosyayı web kökünün dışındaki bir klasöre taşır.
<?php
// İzin verilen türler: gerçek MIME türü => kaydedilecek uzantı
$izinli = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/webp' => 'webp',
'application/pdf' => 'pdf',
];
$azamiBoyut = 5 * 1024 * 1024; // 5 MB
$hedefKlasor = dirname(__DIR__) . '/depo'; // web kökünün DIŞINDA
$dosya = $_FILES['belge'] ?? null;
if ($dosya === null || !is_uploaded_file($dosya['tmp_name']) || $dosya['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
echo 'Dosya yüklenemedi. Lütfen tekrar deneyin.';
return;
}
if ($dosya['size'] <= 0 || $dosya['size'] > $azamiBoyut) {
http_response_code(400);
echo 'Dosya boyutu en fazla 5 MB olabilir.';
return;
}
// Türü tarayıcının bildirdiğinden değil, dosyanın içeriğinden oku
$finfo = new finfo(FILEINFO_MIME_TYPE);
$tur = $finfo->file($dosya['tmp_name']);
if (!isset($izinli[$tur])) {
http_response_code(400);
echo 'Yalnızca JPG, PNG, WebP ve PDF dosyaları kabul edilir.';
return;
}
// Adı sunucu üretir; kullanıcının verdiği ad diskte kullanılmaz
$yeniAd = bin2hex(random_bytes(16)) . '.' . $izinli[$tur];
if (!move_uploaded_file($dosya['tmp_name'], $hedefKlasor . '/' . $yeniAd)) {
error_log('Yüklenen dosya taşınamadı: ' . $yeniAd);
http_response_code(500);
echo 'Dosya kaydedilemedi.';
return;
}
chmod($hedefKlasor . '/' . $yeniAd, 0644);
// Özgün ad yalnız göstermek için veritabanında saklanır
$sorgu = $pdo->prepare('INSERT INTO dosyalar (kullanici_id, disk_adi, ozgun_ad, tur) VALUES (?, ?, ?, ?)');
$sorgu->execute([$kullaniciId, $yeniAd, mb_substr(basename($dosya['name']), 0, 150), $tur]);
Ek öneriler:
- Görselleri yeniden üretin: Yüklenen görseli GD ya da Imagick ile açıp yeniden kaydetmek, dosyanın gerçekten görsel olduğunu doğrular ve içine gömülmüş fazlalıkları atar.
- SVG'ye dikkat: SVG dosyaları betik içerebilir. Gerekmiyorsa izin vermeyin; gerekiyorsa temizleyin ve indirme olarak sunun.
- Arşivleri açmayın: Zip gibi arşivleri sunucuda otomatik açmak, içindeki dosya adları ve boyutlar yüzünden ek riskler getirir.
- Office ve PDF belgeleri: Sunucuda çalışmazlar ama indiren kişi için risk taşıyabilirler; mümkünse zararlı yazılım taramasından geçirin.
Web kökü dışındaki dosyayı sunmak
Dosyalar web kökünün dışındaysa, onları yetki kontrolü yapan küçük bir betik sunar. Betik dosyayı veritabanındaki kayıttan bulur; kullanıcıdan dosya yolu almaz.
<?php
// Dosya kimliğiyle istenir; yol kullanıcıdan alınmaz
$sorgu = $pdo->prepare('SELECT disk_adi, ozgun_ad, tur FROM dosyalar WHERE id = ? AND kullanici_id = ?');
$sorgu->execute([(int) ($_GET['id'] ?? 0), $kullaniciId]);
$kayit = $sorgu->fetch();
$yol = $kayit ? dirname(__DIR__) . '/depo/' . basename($kayit['disk_adi']) : '';
if (!$kayit || !is_file($yol)) {
http_response_code(404);
echo 'Dosya bulunamadı.';
return;
}
header('Content-Type: ' . $kayit['tur']);
header('X-Content-Type-Options: nosniff');
header('Content-Disposition: attachment; filename="' . rawurlencode($kayit['ozgun_ad']) . '"');
header('Content-Length: ' . filesize($yol));
readfile($yol);
X-Content-Type-Options: nosniff başlığı, tarayıcının dosyayı bildirdiğinizden farklı bir tür olarak yorumlamasını engeller. Kullanıcıların yüklediği dosyaları mümkünse attachment olarak (indirme) sunun.
Yükleme klasöründe betik çalıştırmayı kapatın
Dosyalar web kökünün içinde duruyorsa, yükleme klasörüne aşağıdaki gibi bir .htaccess koyun. Bu blok, klasördeki PHP uzantılı dosyalara erişimi tümüyle reddeder; klasöre bir şekilde betik yerleştirilse bile adresle çağrılamaz.
# upload/.htaccess (yükleme klasörünün içine)
# PHP uzantılı dosyalara erişimi reddet
<FilesMatch "(?i)\.(php[0-9]?|phtml|phar|pht)$">
Require all denied
</FilesMatch>
# Ara uzantı olarak .php içeren adlar da (ör. ad.php.jpg) reddedilsin
<FilesMatch "(?i)\.php[0-9]?\.">
Require all denied
</FilesMatch>
# Dizin listeleme ve CGI çalıştırma kapalı
Options -Indexes -ExecCGI
- Bu ayar Apache 2.4 ve
.htaccesskullanımına izin veren barındırmalar içindir. Sunucunuz.htaccessdosyalarını dikkate almıyorsa (ya da Nginx kullanıyorsanız) aynı kural sunucu yapılandırmasında tanımlanmalıdır. - Kuralı test edin: klasöre yalnızca zararsız bir metin yazdıran
.phpuzantılı bir dosya koyup adresini açın; "403 Yasak" yanıtı almalısınız. Sonra dosyayı silin. - Yükleme betiğiniz
.htaccessadlı bir dosyanın yüklenmesine izin vermemelidir; uzantı izin listesi bunu zaten engeller. - Yükleme klasörünün ve içindeki dosyaların çalıştırma izni olmamalı; klasör 755, dosyalar 644 yeterlidir.
Web shell belirtileri
Sunucuya bir arka kapı yerleştirildiğinde site çoğu zaman normal çalışmaya devam eder. Şu belirtilere dikkat edin:
- Beklenmeyen PHP dosyaları: Yükleme, görsel, önbellek ya da geçici klasörlerde
.phpdosyaları; tanımadığınız, rastgele ya da sistem dosyasına benzetilmiş adlar. - Değişen tarihler: Sizin güncelleme yapmadığınız bir tarihte değişmiş çekirdek dosyalar,
index.php,.htaccessya da yapılandırma dosyası. - Dosya içeriği: Normal kodla ilgisi olmayan, çok uzun tek satırlık, anlamsız karakter dizilerinden oluşan bölümler; dosyanın başına ya da sonuna eklenmiş yabancı kod.
- Garip yönlendirmeler: Siteye arama motorundan ya da telefondan gelen ziyaretçilerin başka sitelere yönlendirilmesi; sizin eklemediğiniz bağlantılar ve sayfalar.
- Hesaplar ve görevler: Tanımadığınız yönetici hesapları, FTP hesapları, zamanlanmış görevler (cron).
- Günlükler: Tek bir bilinmeyen PHP dosyasına art arda POST istekleri; yükleme klasöründeki bir dosyaya yapılan istekler.
- Dış uyarılar: Barındırma sağlayıcınızdan kötüye kullanım bildirimi, Google Search Console'da güvenlik sorunu uyarısı, sitenizden gönderilen istenmeyen e-postalar.
İmza tabanlı tarayıcılar yardımcıdır ama tek başına yeterli değildir; tanımadıkları bir arka kapıyı "temiz" gösterebilirler. En güvenilir yöntem, sunucudaki dosyaları temiz kaynakla karşılaştırmaktır: yazılımın özgün paketinde ya da kendi kaynak kodunuzda bulunmayan her dosya ve her fark incelenmelidir.
Zararlı dosya bulduysanız
- Hemen silmeyin, önce kopyalayın. Dosyanın bir kopyasını, değişiklik tarihini ve ilgili günlük kayıtlarını saklayın; giriş noktasını bulmak için gerekecektir.
- Tek dosya olduğunu varsaymayın. Arka kapılar genellikle birden fazla yere bırakılır. Tüm dosyaları temiz kaynakla karşılaştırın.
- Tüm parolaları değiştirin: barındırma paneli, FTP/SFTP, veritabanı, yönetim paneli.
- Giriş noktasını bulun: Dosyanın oluşturulma zamanına yakın günlük kayıtlarına bakın; hangi yükleme formu, hangi eklenti ya da hangi hesap kullanılmış?
- Açığı kapatın ve yazılımı güncelleyin. Yalnızca dosyayı silerseniz aynı yoldan yeniden yüklenir.
Adım adım olay müdahalesi için Siteniz hacklendiyse ne yapmalısınız? rehberine bakın.
Kontrol listesi
- Dosya türü sunucuda içerikten doğrulanıyor; yalnız izin listesindeki türler kabul ediliyor.
- Dosya adı sunucuda rastgele üretiliyor; kullanıcının verdiği ad diskte kullanılmıyor.
- Boyut ve dosya sayısı sınırı var.
- Dosyalar web kökü dışında ya da betik çalıştırmanın kapalı olduğu klasörde.
- Yükleme klasöründeki kural test edildi (PHP dosyası 403 veriyor).
- Yükleme formu oturum ve CSRF korumalı.
- Yükleme klasörleri düzenli olarak beklenmeyen dosyalar için kontrol ediliyor.
- Dosyalar temiz kaynakla karşılaştırılabiliyor (kaynak kodun güncel kopyası ya da dosya özet listesi var).
Sık sorulan sorular
Yalnızca uzantıyı kontrol etmek neden yetmez?
Uzantı dosya adının bir parçasıdır ve kullanıcı tarafından belirlenir; içerik başka bir türde olabilir. Türü içerikten doğrulamak, adı sunucuda üretmek ve klasörde yürütmeyi kapatmak birlikte uygulanmalıdır.
Dosyaları veritabanında saklamak daha mı güvenli?
Yürütme riskini ortadan kaldırır ama veritabanını büyütür ve yedeklemeyi ağırlaştırır. Web kökü dışındaki bir klasör çoğu durumda aynı güvenliği daha az maliyetle sağlar.
Güvenlik taraması temiz dedi; web shell olmadığından emin olabilir miyim?
Hayır. Tarayıcılar bilinen imzaları arar; değiştirilmiş ya da yeni bir arka kapıyı kaçırabilir. Dosyaları temiz kaynakla karşılaştırmak ve beklenmeyen dosyaları ad ve konumlarına göre incelemek daha güvenilirdir.
Yükleme özelliğim yok; yine de risk var mı?
Evet. Web shell, güncellenmemiş bir eklentideki açık ya da ele geçirilmiş bir FTP hesabı üzerinden de yerleştirilebilir. Güncelleme, güçlü parolalar ve dosya değişikliği izleme yine gereklidir.
BYK Yazılım Destek Ekibi
Bu rehber BYK Yazılım destek ekibi tarafından hazırlanır ve düzenli olarak gözden geçirilir. Son güncelleme: 04.10.2026.
İlgili rehberler
Web sitenizi birlikte gözden geçirelim
BYK Yazılım kurumsal web siteleri geliştirir. Sitenizle ilgili sorularınız için bize yazabilirsiniz.
Bize ulaşın Kurumsal web sitesi hizmetimiz