Foruma hoş geldin 👋, Ziyaretçi

Forum içeriğine ve tüm hizmetlerimize erişim sağlamak için foruma kayıt olmalı ya da giriş yapmalısınız. Foruma üye olmak tamamen ücretsizdir.

API'lerde güvenli file upload işlemi için dosya tipi, boyutu ve virus taraması kontrollerimi nasıl eklerim?

devnix

Üye
Katılım
14 Mart 2026
Mesajlar
40
Merhaba arkadaşlar, bugün başımı çok ağrıtan bir konuyu, güvenli dosya yükleme işlemini nasıl sağlamlaştırdığımı anlatacağım. Bir Node.js API projesinde, kullanıcıların profil resmi yükleyebilmesi gerekiyordu. İlk başta basit bir `multer` middleware'i ile işi bitirdim sanmıştım. Ta ki bir kullanıcının 500MB'lık bir video yüklemeye çalıştığı ana kadar! Sunucu kaynakları allak bullak oldu. O günden sonra "dosya yükleme" deyip geçmemeyi öğrendim. İşte benim uyguladığım, üç katmanlı güvenlik kontrolü.

🔥 Karşılaştığım Sorun ve Riskler
Sadece dosya yüklemek değil, asıl mesele kötü niyetli içerikleri engellemek. Düşünsenize, birisi `.php` veya `.exe` uzantılı bir dosyayı, resimmiş gibi gönderip sunucuda çalıştırmaya çalışabilir. Ya da basit bir resim dosyasının içine gizlenmiş bir virüs? İşin bir de performans boyutu var. Limitsiz dosya boyutu, sunucunuzu DDoS'a açık hale getirir. Bu yüzden kontrolleri hem istemci tarafında (kullanıcı deneyimi için) hem de kesinlikle sunucu tarafında (güvenlik için) yapmalısınız.

📏 1. Katman: Dosya Boyutu ve Tipi Validasyonu (Node.js + Multer)
İlk savunma hattımız, dosyanın gelir gelmez kontrol edildiği middleware katmanı. Multer'in limits ve fileFilter özelliklerini kullanıyorum.

JavaScript:
const multer = require('multer');
const path = require('path');

// 1. İzin verilen dosya türlerini tanımla
const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
// 2. MIME type'a göre izin verilen uzantıları eşleştir (çift kontrol için)
const allowedExtensions = {
    'image/jpeg': '.jpg',
    'image/png': '.png',
    'image/gif': '.gif',
    'application/pdf': '.pdf'
};

const upload = multer({
    storage: multer.memoryStorage(), // Dosyayı önce belleğe al, tarama yapacağız.
    limits: {
        fileSize: 5  1024  1024, // Maksimum 5MB
    },
    fileFilter: (req, file, cb) => {
        // MIME Type kontrolü
        if (!allowedMimeTypes.includes(file.mimetype)) {
            return cb(new Error('Sadece JPG, PNG, GIF veya PDF dosyaları yükleyebilirsiniz.'), false);
        }

        // Uzantı kontrolü (MIME spoofing'e karşı)
        const fileExt = path.extname(file.originalname).toLowerCase();
        const expectedExt = allowedExtensions[file.mimetype];

        if (fileExt !== expectedExt) {
            return cb(new Error(`Dosya uzantısı (${fileExt}) beklenen uzantı (${expectedExt}) ile uyuşmuyor.`), false);
        }

        // Magic Number kontrolü (gerçek dosya tipini ilk birkaç byte'dan kontrol et)
        // Bu kısım için ayrı bir fonksiyon yazılabilir, örnek olarak koydum.
        cb(null, true);
    }
});

// Router'da kullanımı
app.post('/upload', upload.single('file'), (req, res) => {
    // Buraya geldiyse, Multer temel kontrolleri geçti demektir.
    res.json({ message: 'Dosya başarıyla alındı. İşleme devam ediliyor...' });
});

Burada dikkat ettiğim en önemli nokta, sadece uzantıya (`originalname`) veya sadece MIME type'a (`mimetype`) güvenmemek. İkisini birlikte kontrol ediyorum. Ayrıca dosyayı diske hemen yazmıyorum, `memoryStorage` ile belleğe alıyorum ki bir sonraki katmanda (virüs taraması) işlem yapabilelim.

🛡️ 2. Katman: Virüs/Malware Taraması (ClamAV ile)
Multer'den geçen dosya, henüz masum olduğunu kanıtlamadı! Bu yüzden bir antivirüs motoru kullanmak şart. Ben açık kaynaklı ClamAV'yi tercih ediyorum. Node.js'den `clamscan` paketi ile entegre edebilirsiniz.

JavaScript:
const { promisify } = require('util');
const clamscan = require('clamscan')({
    remove_infected: false, // Dosyayı otomatik silme, logla ve kullanıcıya hata dön.
    quarantine_infected: false,
    scan_log: '/var/log/clamav/scan.log',
    debug_mode: false,
    file_list: [], // Bellekteki buffer'ı tarayacağız.
    scan_recursively: false,
    clamscan: {
        path: '/usr/bin/clamscan', // Sunucudaki ClamAV binary yolu
        db: null, // Varsayılan veritabanı
        scan_archives: true,
        active: true
    }
}).get_instance();
const clamScanPromise = promisify(clamscan.scan_buffer);

async function scanFileForViruses(fileBuffer, fileName) {
    try {
        const { is_infected, viruses } = await clamScanPromise(fileBuffer, fileName);
        if (is_infected) {
            console.error(`[VIRUS TARAMA HATASI] ${fileName} dosyasında virüs tespit edildi: ${viruses}`);
            throw new Error(`Dosya güvenlik taramasından geçemedi. Lütfen temiz bir dosya yükleyin.`);
        }
        console.log(`[GÜVENLİ] ${fileName} dosyası temiz.`);
        return true;
    } catch (error) {
        console.error('[ClamAV Tarama Hatası]', error);
        // ClamAV çalışmıyorsa, dosyayı kabul ETME! Güvenlik önceliklidir.
        throw new Error('Dosya güvenlik kontrolü şu an yapılamıyor. Lütfen daha sonra tekrar deneyin.');
    }
}

// Upload route'ını güncelleme
app.post('/upload', upload.single('file'), async (req, res, next) => {
    try {
        if (!req.file) {
            return res.status(400).json({ error: 'Dosya yüklenmedi.' });
        }

        // 1. Multer kontrollerinden geçti.
        // 2. Şimdi virüs taraması yapalım.
        await scanFileForViruses(req.file.buffer, req.file.originalname);

        // 3. Eğer buraya geldiyse, dosya temiz. Artık kalıcı depolamaya yazabiliriz (S3, lokal disk vs.).
        // const fileUrl = await saveFileToStorage(req.file.buffer, req.file.originalname);
        // ... kayıt işlemleri ...

        res.json({ message: 'Dosya güvenli bir şekilde yüklendi ve taranmıştır.', url: fileUrl });
    } catch (error) {
        next(error); // Hataları merkezi error handler'a gönder
    }
});

Bu katman olmazsa olmaz. ClamAV sürekli güncellenen bir virüs veritabanına sahip. Tarama başarısız olursa (ClamAV servisi çalışmıyorsa), dosyayı kesinlikle kabul etmiyorum. "Aman canım bir seferlik" demek, tüm sistemi riske atmaktır.

✅ 3. Katman: Son Kontroller ve Depolama
Her şey yolunda gittiyse, son adıma geçebiliriz. Dosyayı güvenli bir şekilde depolamak. Dosya isimlerini kullanıcının verdiği isimle (`originalname`) kaydetmeyin! Bu hem güvenlik açığı (path traversal) hem de çakışmaya neden olur. Rastgele, unique bir isim üretin.

JavaScript:
const crypto = require('crypto');
const fs = require('fs').promises;
const path = require('path');

async function saveFileToStorage(fileBuffer, originalName) {
    // 1. Güvenli, rastgele dosya ismi oluştur.
    const fileExt = path.extname(originalName); // .jpg, .png
    const randomName = crypto.randomBytes(16).toString('hex') + fileExt;

    // 2. Kaydedilecek güvenli yol (public klasörü dışında bir yerde tutun, sonra serve edin).
    const uploadDir = path.join(__dirname, '..', 'private-uploads');
    const filePath = path.join(uploadDir, randomName);

    // 3. Klasör yoksa oluştur.
    await fs.mkdir(uploadDir, { recursive: true });

    // 4. Buffer'ı dosyaya yaz.
    await fs.writeFile(filePath, fileBuffer);

    // 5. Erişim için bir URL döndür (örneğin, bir static file servisi üzerinden).
 

Tema özelleştirme sistemi

Bu menüden forum temasının bazı alanlarını kendinize özel olarak düzenleye bilirsiniz.

Zevkine göre renk kombinasyonunu belirle

Tam ekran yada dar ekran

Temanızın gövde büyüklüğünü sevkiniz, ihtiyacınıza göre dar yada geniş olarak kulana bilirsiniz.

Geri