Zararsız Görünen Bir 'Profil Fotoğrafı' Alanı Sunucuyu Nasıl Ele Geçirtir? — Kritik RCE Senaryosu
Güvenlik dünyasında en pahalı cümlelerden biri şudur: "Sadece bir resim yükleme alanı, ne olabilir ki?" Anonimleştirilmiş bu senaryo, o sorunun cevabını veriyor: tüm sunucu.
Sahne: Profil Fotoğrafı
Uygulamada kullanıcılar profil fotoğrafı yükleyebiliyordu. Beklenen: .jpg, .png. Sunucu ise gelen dosyayı /uploads/ klasörüne, gönderilen adla kaydediyor ve bu klasör web'den doğrudan erişilebilir durumda.
İki ölümcül varsayım: (1) dosya adına güveniliyor, (2) yüklenen klasördeki her şey sunucuda çalıştırılabiliyor.
Adım 1: Resim Yerine Kod
Saldırgan bir resim yerine, adı foto.php olan tek satırlık bir dosya gönderir:
<?php system($_GET['c']); ?>
Uygulama uzantıyı kontrol etmediği için dosyayı kabul eder ve şuraya yazar:
https://uygulama.ornek/uploads/foto.php
Adım 2: Webshell
Artık saldırganın sunucuda komut çalıştırmasına "webshell" denir. Tarayıcıdan ya da curl ile:
curl "https://uygulama.ornek/uploads/foto.php?c=id" # çıktı: uid=33(www-data) gid=33(www-data) ...
Sunucu, saldırganın gönderdiği id komutunu çalıştırıp cevabı döndürdü. Buna RCE (Remote Code Execution / Uzaktan Kod Çalıştırma) denir — açıkların en kritiği.
Adım 3: Zincirin Sonu
Bir kez komut çalıştıran saldırgan tipik olarak:
- Veritabanı bağlantı bilgilerini okur (
config.php) - Kalıcı erişim için arka kapı bırakır
- Yetki yükseltip (privilege escalation) tüm sunucuya, oradan iç ağa geçmeyi dener
"Bir resim alanı", kurumun iç ağına açılan kapıya dönüşür.
Çözüm: Katmanlı Savunma
Tek bir kontrol yeterli değildir; hepsi birlikte uygulanır.
1. Uzantı ve içerik tipi doğrulama (izin listesi). Neyin yasak olduğunu değil, neyin serbest olduğunu tanımlayın:
IZINLI = {"jpg", "jpeg", "png", "webp"}
uzanti = ad.rsplit(".", 1)[-1].lower()
if uzanti not in IZINLI:
return hata("Yalnızca resim yüklenebilir.")2. Dosyanın gerçekten resim olduğunu doğrula. Uzantı yalan söyleyebilir; dosyanın "sihirli baytlarına" (magic bytes) bakın veya bir resim kütüphanesiyle açmayı deneyin:
from PIL import Image
try:
Image.open(dosya).verify() # açılmıyorsa resim değildir
except Exception:
return hata("Geçersiz resim.")3. Adı siz belirleyin. Kullanıcının verdiği adı asla kullanmayın; rastgele bir ad üretin: a91f3c...png.
4. En kritiği — çalıştırmayı engelleyin. Yüklenen klasörü web kök dizininin dışında tutun ya da o klasörde kod yürütmeyi kapatın. nginx örneği:
location /uploads/ {
location ~ \.php$ { deny all; }
}Böylece foto.php yüklense bile çalışmaz, sadece metin dosyası olarak durur.
Etik ve yasal not: Webshell yükleme ve komut çalıştırma yalnızca yazılı yetki verilmiş test ortamlarında yapılır. İzinsiz yapıldığında ağır cezai sorumluluk doğurur. REDCELL bu testleri kontrollü, sözleşmeli kapsamda ve geri alınabilir biçimde yürütür.
Sonuç
Dosya yükleme alanları, modern web'in en sık ihmal edilen kritik kapılarıdır. Sitenizde kullanıcı dosya yükleyebiliyorsa — profil fotoğrafı, CV, belge, ne olursa — bu zincirin sizde kurulup kurulamayacağı bugün test edilmelidir. REDCELL, tam da bu zinciri güvenli biçimde deneyip size kapatma reçetesini verir.
Sisteminizin güvenliğinden emin misiniz?
Ücretsiz Ön Değerlendirme →