File Upload Güvenliği (Detaylı Anlatım ve Güvenli Uygulama Teknikleri)
File Upload Güvenliği, web uygulamalarının kullanıcılar tarafından yüklenen dosyaları güvenli bir şekilde işlemesini sağlamak için alınması gereken önlemleri kapsar. Güvensiz dosya yükleme mekanizmaları, sunucunun ele geçirilmesine, veri sızıntısına ve hizmet kesintilerine yol açabilir.
Dosya Yükleme Zafiyetleri Nelerdir?
Kötü Amaçlı Dosya Yükleme: PHP, ASP, JSP gibi çalıştırılabilir dosyaların yüklenmesi
MIME Tipi Sahteciliği: Content-Type header'ının manipüle edilmesi
Çift Uzantı Saldırısı: image.jpg.php gibi dosya adları
Null Byte Enjeksiyonu: image.php%00.jpg ile uzantı atlatma
Dosya Boyutu Saldırıları: Çok büyük dosyalarla DoS saldırısı
Path Traversal: ../../../etc/passwd ile dizin atlama
XSS ve HTML Enjeksiyonu: SVG, HTML dosyaları ile
Zip Slip: Arşiv dosyaları ile dizin atlama
Güvenli Dosya Yükleme İçin Uygulama Teknikleri:
1. Dosya Türü Doğrulama (File Type Validation)
Yanlış Yaklaşım:
python
# SADECE uzantı kontrolü (güvensiz)
if not filename.endswith('.jpg'):
raise Exception("Geçersiz dosya türü")
Doğru Yaklaşım:
python
import imghdr
import magic
def validate_file_type(file_content, filename):
# 1. Magic bytes ile dosya imzasını kontrol et
mime = magic.from_buffer(file_content[:1024], mime=True)
# 2. İzin verilen MIME tipleri
ALLOWED_MIMES = [
'image/jpeg', 'image/png', 'image/gif',
'image/webp', 'application/pdf'
]
if mime not in ALLOWED_MIMES:
raise Exception(f"Geçersiz dosya türü: {mime}")
# 3. Ek kontrol - görsel dosyalar için
if mime.startswith('image/'):
try:
import PIL.Image
img = PIL.Image.open(io.BytesIO(file_content))
img.verify() # Bozuk dosya kontrolü
except:
raise Exception("Geçersiz görsel dosyası")
return mime
2. Dosya Adı Güvenliği
Kötü Uygulama:
Kullanıcı dosya adını doğrudan kullanma
Özel karakterleri filtrelememe
Güvenli Uygulama:
python
import uuid
import os
import re
def safe_filename(original_filename):
# 1. Benzersiz ID oluştur
unique_id = uuid.uuid4().hex
# 2. Orijinal uzantıyı al (güvenli şekilde)
extension = os.path.splitext(original_filename)[1].lower()
# 3. Sadece güvenli uzantılara izin ver
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.pdf', '.webp'}
if extension not in ALLOWED_EXTENSIONS:
raise Exception("Geçersiz dosya uzantısı")
# 4. Yeni dosya adı oluştur (tahmin edilemez)
return f"{unique_id}{extension}"
# Kullanıcının orijinal adını ayrı bir alanda sakla (isteğe bağlı)
original_name = request.files['file'].filename
safe_name = safe_filename(original_name)
3. Dosya Boyutu Sınırlaması
python
# Flask örneği
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 # 5MB
# Manuel kontrol
def validate_file_size(file_content):
MAX_SIZE = 5 * 1024 * 1024 # 5MB
if len(file_content) > MAX_SIZE:
raise Exception("Dosya boyutu 5MB'ı geçemez")
# ZIP bomb kontrolü (sıkıştırılmış dosya)
if file_content.startswith(b'PK'): # ZIP dosyası
import zipfile
with zipfile.ZipFile(io.BytesIO(file_content)) as zf:
total_size = sum(info.file_size for info in zf.infolist())
if total_size > 100 * 1024 * 1024: # 100MB
raise Exception("ZIP dosyası çok büyük")
4. Dosya İçeriği Temizleme (Sanitization)
python
import io
from PIL import Image
def sanitize_image(file_content):
try:
# 1. Görseli yeniden oluştur (metadata temizleme)
img = Image.open(io.BytesIO(file_content))
# 2. Görseli yeniden kaydet (EXIF metadata temizle)
output = io.BytesIO()
img.save(output, format=img.format)
# 3. Boyut kontrolü
if img.width > 4000 or img.height > 4000:
raise Exception("Görsel boyutları çok büyük")
return output.getvalue()
except Exception as e:
raise Exception(f"Görsel işlenemedi: {str(e)}")
5. Güvenli Depolama
Kötü Uygulama:
python
# Dosyayı web root altında saklama
file.save('/var/www/html/uploads/' + filename)
Güvenli Uygulama:
python
import os
def save_file_safely(file_content, safe_filename):
# 1. Web root dışında sakla
UPLOAD_DIR = '/var/data/uploads/'
# 2. Tarih bazlı alt dizin oluştur
import datetime
date_path = datetime.datetime.now().strftime('%Y/%m/%d')
full_path = os.path.join(UPLOAD_DIR, date_path)
os.makedirs(full_path, exist_ok=True)
# 3. Dosyayı kaydet
file_path = os.path.join(full_path, safe_filename)
with open(file_path, 'wb') as f:
f.write(file_content)
# 4. Dosya izinlerini sıkılaştır
os.chmod(file_path, 0o644) # Sadece okuma izni
return file_path
6. İndirme Mekanizması Güvenliği
Kötü Uygulama:
python
# Doğrudan dosya sunma
return send_file('/var/data/uploads/' + filename)
Güvenli Uygulama:
python
from flask import send_file, abort
def download_file(file_id, current_user):
# 1. Dosyayı veritabanından getir
file_record = File.objects.get(id=file_id)
# 2. Yetkilendirme kontrolü
if file_record.user_id != current_user.id:
abort(403)
# 3. Dosya yolunu güvenli şekilde oluştur
file_path = os.path.join(UPLOAD_DIR, file_record.stored_path)
# 4. Path traversal kontrolü
real_path = os.path.realpath(file_path)
if not real_path.startswith(os.path.realpath(UPLOAD_DIR)):
abort(403)
# 5. Dosyayı stream et
return send_file(real_path,
as_attachment=True,
download_name=file_record.original_name)
7. Virüs Taraması
python
import subprocess
def scan_file(file_path):
# ClamAV ile tarama
try:
result = subprocess.run(
['clamscan', file_path],
capture_output=True,
timeout=30
)
if b'FOUND' in result.stdout:
raise Exception("Virüs tespit edildi")
return True
except subprocess.TimeoutExpired:
raise Exception("Virüs taraması zaman aşımına uğradı")
8. Rate Limiting ve Kaynak Koruması
python
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
@app.route('/upload', methods=['POST'])
@limiter.limit("10 per minute") # Dakikada 10 dosya
def upload_file():
# Upload işlemi
pass
Dosya Yükleme Güvenliği Kontrol Listesi:
A. Yükleme Aşaması:
Dosya uzantısı beyaz listesi kullanılıyor
MIME tipi kontrolü (magic bytes) yapılıyor
Dosya boyutu sınırlandırılmış
Dosya adı rastgele ve tahmin edilemez
Zararlı içerik temizlenmiş
Virüs taraması yapılmış
Rate limiting uygulanmış
B. Depolama Aşaması:
Dosyalar web root dışında saklanıyor
Dosya izinleri sıkılaştırılmış
Tarih bazlı dizin yapısı kullanılıyor
Şifreli depolama (opsiyonel)
C. Erişim Aşaması:
Yetkilendirme kontrolü yapılıyor
Path traversal engellenmiş
İndirme logları tutuluyor
Dosya türüne göre content-type doğru ayarlanıyor
D. İzleme ve Log:
Tüm yükleme ve indirme işlemleri loglanıyor
Anormal aktiviteler izleniyor
Hata mesajları bilgi sızdırmıyor
Sık Yapılan Hatalar ve Çözümleri:
Hata Risk Çözüm
Sadece uzantı kontrolü MIME sahteciliği Magic bytes kontrolü ekle
Kullanıcı dosya adını kullanma Path traversal Rastgele yeniden adlandır
Web root altında saklama Doğrudan erişim Web root dışında sakla
Content-Type header'ına güvenme MIME sahteciliği Gerçek dosya içeriğini kontrol et
Hata mesajlarını gösterme Bilgi sızıntısı Genel hata mesajları göster
Virüs taraması yapmama Zararlı dosya ClamAV veya benzeri kullan
Yetkilendirme kontrolü atlama IDOR Her dosya için yetki kontrolü yap
File Upload Güvenliği, web uygulamalarının kullanıcılar tarafından yüklenen dosyaları güvenli bir şekilde işlemesini sağlamak için alınması gereken önlemleri kapsar. Güvensiz dosya yükleme mekanizmaları, sunucunun ele geçirilmesine, veri sızıntısına ve hizmet kesintilerine yol açabilir.
Dosya Yükleme Zafiyetleri Nelerdir?
Kötü Amaçlı Dosya Yükleme: PHP, ASP, JSP gibi çalıştırılabilir dosyaların yüklenmesi
MIME Tipi Sahteciliği: Content-Type header'ının manipüle edilmesi
Çift Uzantı Saldırısı: image.jpg.php gibi dosya adları
Null Byte Enjeksiyonu: image.php%00.jpg ile uzantı atlatma
Dosya Boyutu Saldırıları: Çok büyük dosyalarla DoS saldırısı
Path Traversal: ../../../etc/passwd ile dizin atlama
XSS ve HTML Enjeksiyonu: SVG, HTML dosyaları ile
Zip Slip: Arşiv dosyaları ile dizin atlama
Güvenli Dosya Yükleme İçin Uygulama Teknikleri:
1. Dosya Türü Doğrulama (File Type Validation)
Yanlış Yaklaşım:
python
# SADECE uzantı kontrolü (güvensiz)
if not filename.endswith('.jpg'):
raise Exception("Geçersiz dosya türü")
Doğru Yaklaşım:
python
import imghdr
import magic
def validate_file_type(file_content, filename):
# 1. Magic bytes ile dosya imzasını kontrol et
mime = magic.from_buffer(file_content[:1024], mime=True)
# 2. İzin verilen MIME tipleri
ALLOWED_MIMES = [
'image/jpeg', 'image/png', 'image/gif',
'image/webp', 'application/pdf'
]
if mime not in ALLOWED_MIMES:
raise Exception(f"Geçersiz dosya türü: {mime}")
# 3. Ek kontrol - görsel dosyalar için
if mime.startswith('image/'):
try:
import PIL.Image
img = PIL.Image.open(io.BytesIO(file_content))
img.verify() # Bozuk dosya kontrolü
except:
raise Exception("Geçersiz görsel dosyası")
return mime
2. Dosya Adı Güvenliği
Kötü Uygulama:
Kullanıcı dosya adını doğrudan kullanma
Özel karakterleri filtrelememe
Güvenli Uygulama:
python
import uuid
import os
import re
def safe_filename(original_filename):
# 1. Benzersiz ID oluştur
unique_id = uuid.uuid4().hex
# 2. Orijinal uzantıyı al (güvenli şekilde)
extension = os.path.splitext(original_filename)[1].lower()
# 3. Sadece güvenli uzantılara izin ver
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.pdf', '.webp'}
if extension not in ALLOWED_EXTENSIONS:
raise Exception("Geçersiz dosya uzantısı")
# 4. Yeni dosya adı oluştur (tahmin edilemez)
return f"{unique_id}{extension}"
# Kullanıcının orijinal adını ayrı bir alanda sakla (isteğe bağlı)
original_name = request.files['file'].filename
safe_name = safe_filename(original_name)
3. Dosya Boyutu Sınırlaması
python
# Flask örneği
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 # 5MB
# Manuel kontrol
def validate_file_size(file_content):
MAX_SIZE = 5 * 1024 * 1024 # 5MB
if len(file_content) > MAX_SIZE:
raise Exception("Dosya boyutu 5MB'ı geçemez")
# ZIP bomb kontrolü (sıkıştırılmış dosya)
if file_content.startswith(b'PK'): # ZIP dosyası
import zipfile
with zipfile.ZipFile(io.BytesIO(file_content)) as zf:
total_size = sum(info.file_size for info in zf.infolist())
if total_size > 100 * 1024 * 1024: # 100MB
raise Exception("ZIP dosyası çok büyük")
4. Dosya İçeriği Temizleme (Sanitization)
python
import io
from PIL import Image
def sanitize_image(file_content):
try:
# 1. Görseli yeniden oluştur (metadata temizleme)
img = Image.open(io.BytesIO(file_content))
# 2. Görseli yeniden kaydet (EXIF metadata temizle)
output = io.BytesIO()
img.save(output, format=img.format)
# 3. Boyut kontrolü
if img.width > 4000 or img.height > 4000:
raise Exception("Görsel boyutları çok büyük")
return output.getvalue()
except Exception as e:
raise Exception(f"Görsel işlenemedi: {str(e)}")
5. Güvenli Depolama
Kötü Uygulama:
python
# Dosyayı web root altında saklama
file.save('/var/www/html/uploads/' + filename)
Güvenli Uygulama:
python
import os
def save_file_safely(file_content, safe_filename):
# 1. Web root dışında sakla
UPLOAD_DIR = '/var/data/uploads/'
# 2. Tarih bazlı alt dizin oluştur
import datetime
date_path = datetime.datetime.now().strftime('%Y/%m/%d')
full_path = os.path.join(UPLOAD_DIR, date_path)
os.makedirs(full_path, exist_ok=True)
# 3. Dosyayı kaydet
file_path = os.path.join(full_path, safe_filename)
with open(file_path, 'wb') as f:
f.write(file_content)
# 4. Dosya izinlerini sıkılaştır
os.chmod(file_path, 0o644) # Sadece okuma izni
return file_path
6. İndirme Mekanizması Güvenliği
Kötü Uygulama:
python
# Doğrudan dosya sunma
return send_file('/var/data/uploads/' + filename)
Güvenli Uygulama:
python
from flask import send_file, abort
def download_file(file_id, current_user):
# 1. Dosyayı veritabanından getir
file_record = File.objects.get(id=file_id)
# 2. Yetkilendirme kontrolü
if file_record.user_id != current_user.id:
abort(403)
# 3. Dosya yolunu güvenli şekilde oluştur
file_path = os.path.join(UPLOAD_DIR, file_record.stored_path)
# 4. Path traversal kontrolü
real_path = os.path.realpath(file_path)
if not real_path.startswith(os.path.realpath(UPLOAD_DIR)):
abort(403)
# 5. Dosyayı stream et
return send_file(real_path,
as_attachment=True,
download_name=file_record.original_name)
7. Virüs Taraması
python
import subprocess
def scan_file(file_path):
# ClamAV ile tarama
try:
result = subprocess.run(
['clamscan', file_path],
capture_output=True,
timeout=30
)
if b'FOUND' in result.stdout:
raise Exception("Virüs tespit edildi")
return True
except subprocess.TimeoutExpired:
raise Exception("Virüs taraması zaman aşımına uğradı")
8. Rate Limiting ve Kaynak Koruması
python
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
@app.route('/upload', methods=['POST'])
@limiter.limit("10 per minute") # Dakikada 10 dosya
def upload_file():
# Upload işlemi
pass
Dosya Yükleme Güvenliği Kontrol Listesi:
A. Yükleme Aşaması:
Dosya uzantısı beyaz listesi kullanılıyor
MIME tipi kontrolü (magic bytes) yapılıyor
Dosya boyutu sınırlandırılmış
Dosya adı rastgele ve tahmin edilemez
Zararlı içerik temizlenmiş
Virüs taraması yapılmış
Rate limiting uygulanmış
B. Depolama Aşaması:
Dosyalar web root dışında saklanıyor
Dosya izinleri sıkılaştırılmış
Tarih bazlı dizin yapısı kullanılıyor
Şifreli depolama (opsiyonel)
C. Erişim Aşaması:
Yetkilendirme kontrolü yapılıyor
Path traversal engellenmiş
İndirme logları tutuluyor
Dosya türüne göre content-type doğru ayarlanıyor
D. İzleme ve Log:
Tüm yükleme ve indirme işlemleri loglanıyor
Anormal aktiviteler izleniyor
Hata mesajları bilgi sızdırmıyor
Sık Yapılan Hatalar ve Çözümleri:
Hata Risk Çözüm
Sadece uzantı kontrolü MIME sahteciliği Magic bytes kontrolü ekle
Kullanıcı dosya adını kullanma Path traversal Rastgele yeniden adlandır
Web root altında saklama Doğrudan erişim Web root dışında sakla
Content-Type header'ına güvenme MIME sahteciliği Gerçek dosya içeriğini kontrol et
Hata mesajlarını gösterme Bilgi sızıntısı Genel hata mesajları göster
Virüs taraması yapmama Zararlı dosya ClamAV veya benzeri kullan
Yetkilendirme kontrolü atlama IDOR Her dosya için yetki kontrolü yap
🔒 Bu içeriği görmek için giriş yapın