Konuyu Açan
#0
Dosya yükleme sistemleri, web uygulamalarının olmazsa olmazı diyebiliriz; profil resimlerinden belge paylaşımlarına, her yerde karşımıza çıkıyorlar. Ama işin aslı, bu basit görünen mekanizma, güvenlik açısından en çok dikkat etmemiz gereken yerlerden biri... Genelde "bir ile hallederiz" diye düşünülür, ancak o kadar basit değil, inanın bana.
Öncelikle, işin HTML kısmını doğru kurmakla başlayalım. Bir dosya yükleme formu oluştururken, sıradan formlardan biraz farklı bir kullanmamız gerekiyor. Eğer bunu unutursanız, PHP tarafında süperglobali bomboş kalır, dosya falan göremezsiniz. İşte temel bir HTML formu:
Buradaki kısmı çok önemli, çünkü PHP tarafında bu isimle dosyaya erişeceğiz. Bir de ve olmazsa olmazlar arasında, aksi halde tarayıcı dosyayı server'a düzgün bir şekilde göndermez. Bu detaylar atlandığında çok vakit kaybedilebilir, tecrübeyle sabit...
Şimdi gelelim PHP tarafına, yani dosyamıza. Dosya yüklendiğinde, tüm bilgiler süperglobal dizisinde toplanır. Bu dizi, formda verdiğiniz attribute'u ile erişilebilir. Örneğin, yukarıdaki form için şeklinde erişiriz. İçinde neler mi var?
Dosya yükleme sistemlerinin en hassas noktası, güvenlik. Bir kere, kullanıcıların ne yükleyebileceğini kısıtlamamız şart. Sadece resim bekliyorsanız, birisi gelip .exe veya .php dosyası yüklemeye çalışmamalı. İşte burada MIME tipi ve dosya uzantısı kontrolü devreye giriyor. Ben genellikle ikisini birlikte kontrol etmeyi tercih ederim, çünkü MIME tipi bazen kolayca taklit edilebilir. Örneğin:
Bu kod bloğu, temel kontrolleri içeriyor. fonksiyonu, özellikle resim dosyaları için harika bir güvenlik katmanı sağlar; dosyanın gerçekten bir resim olup olmadığını, sadece uzantısına bakarak değil, içeriğine bakarak anlarız. Eğer geçersiz bir resim dosyasıysa, döner.
Peki ya dosya adları? Kullanıcının yüklediği dosyanın adını olduğu gibi kaydetmek, ciddi riskler barındırır. Diyelim ki bir kullanıcı gibi bir isimle dosya yüklemeye çalıştı veya adında kötü niyetli bir script yükledi... İşte bu yüzden, yüklenen her dosyaya benzersiz, rastgele bir isim vermek en sağlıklısıdır. Şöyle bir şey düşünebiliriz:
Böylece hem çakışmaların önüne geçeriz hem de potansiyel yol geçişi (path traversal) saldırılarını engelleriz. Orijinal dosya adını kaydetmek isterseniz, onu veritabanına ayrı bir sütun olarak saklayabilirsiniz, ama asla sunucuda dosya adı olarak kullanmayın.
Bir de hedef dizinin yazma izinleri konusu var, yani klasörümüzün. PHP'nin bu klasöre dosya yazabilmesi için gerekli izinlere sahip olması gerekir. Genellikle veya (sunucu yapılandırmasına göre değişebilir) yeterli olur. Eğer izinler yanlışsa, fonksiyonu her zaman hata verir ve "Permission denied" gibi bir mesajla karşılaşırsınız. Bu durumla çok sık karşılaşılır, özellikle yeni başlayanlar için tam bir baş belasıdır.
kısmına da değinmek lazım. Bu, yükleme işlemi sırasında PHP'nin otomatik olarak oluşturduğu hata kodlarını içerir.
Dosyayı sunucuya taşıma işlemi için fonksiyonunu kullanıyoruz. Bu fonksiyon, PHP'nin güvenlik mekanizmalarıyla birlikte çalıştığı için, doğrudan veya kullanmaktan çok daha güvenlidir. Yüklenen dosyanın gerçekten bir HTTP POST yüklemesi olup olmadığını kontrol eder. İlk parametre geçici dosya yolu, ikincisi ise hedef yol ve dosya adıdır.
Buradaki kullanımı da çok ince bir detaydır. Yüklenen dosya adını (veya yeni adını) ekrana bastırırken olası XSS (Cross-Site Scripting) saldırılarına karşı önlem almış oluruz. Küçük bir dokunuş gibi görünse de, güvenlik zincirinin önemli bir halkasıdır.
Peki, yüklenen dosyalarla ilgili bilgileri veritabanına kaydetmeli miyiz? Kesinlikle evet! Dosyanın orijinal adı, yeni adı, boyutu, MIME tipi, yüklenme tarihi ve kim tarafından yüklendiği gibi bilgileri veritabanında tutmak, hem daha düzenli bir yapı sağlar hem de ileride raporlama veya dosya yönetimi açısından işimizi çok kolaylaştırır. Örneğin, bir kullanıcının yüklediği tüm resimleri listelemek istediğinizde, veritabanındaki bilgilere dayanarak bunu çok rahat yapabilirsiniz. Bir örnek tablo yapısı şöyle olabilir:
Bu sayede, dosya sisteminde sadece benzersiz isimli dosyalar dururken, tüm meta verileri düzenli bir şekilde yönetmiş oluruz.
Çoklu dosya yükleme konusu da var elbette. Bu durumda HTML formunda etiketine attribute'unu ekleriz ve attribute'unu bir dizi olarak tanımlarız, örneğin . PHP tarafında ise dizisi, her bir dosya için ayrı ayrı bilgiler içeren iç içe bir dizi haline gelir. Her dosyayı bir döngü içinde tek tek işlemek gerekir. Bu da biraz daha kod kalabalığı demek ama mantık aynıdır.
Sonuç olarak, PHP ile dosya yükleme sistemi geliştirmek, sadece fonksiyonunu kullanmaktan çok daha fazlasını gerektirir. Güvenlik kontrolleri, dosya adı yönetimi, veritabanı entegrasyonu ve kullanıcıya doğru geri bildirimler sunmak, sağlam ve güvenilir bir sistemin temel taşlarıdır. Bu adımların her biri atlanmaması gereken kritik öneme sahiptir.
Peki sizler, dosya yükleme sistemleri geliştirirken en çok hangi güvenlik önlemlerine dikkat ediyorsunuz? Ya da başınıza gelen en ilginç dosya yükleme hatası senaryosu neydi? Tecrübelerinizi ve görüşlerinizi merak ediyorum...
CODE
1move_uploaded_file()Öncelikle, işin HTML kısmını doğru kurmakla başlayalım. Bir dosya yükleme formu oluştururken, sıradan formlardan biraz farklı bir
CODE
1enctypeCODE
1$_FILESCODE
123456
Yüklenecek Dosyayı Seçin:
Buradaki
CODE
1name="fileToUpload"CODE
1method="post"CODE
1enctype="multipart/form-data"Şimdi gelelim PHP tarafına, yani
CODE
1upload.phpCODE
1$_FILESCODE
1nameCODE
1$_FILES['fileToUpload']- : Dosyanın orijinal adı. Kullanıcının bilgisayarındaki adı neyse o.CODE
1
name
- : Dosyanın MIME tipi (örn:CODE
1
type,CODE1
image/jpeg). Bu, güvenlik için çok kritik bir bilgi.CODE1
application/pdf
- : Sunucuya geçici olarak kaydedilen dosyanın yolu ve adı. Asıl işimiz bununla.CODE
1
tmp_name
- : Yükleme sırasında oluşan hata kodu. EğerCODE
1
errorise her şey yolunda demektir.CODE1
0
- : Dosyanın boyutu (bayt cinsinden).CODE
1
size
Dosya yükleme sistemlerinin en hassas noktası, güvenlik. Bir kere, kullanıcıların ne yükleyebileceğini kısıtlamamız şart. Sadece resim bekliyorsanız, birisi gelip .exe veya .php dosyası yüklemeye çalışmamalı. İşte burada MIME tipi ve dosya uzantısı kontrolü devreye giriyor. Ben genellikle ikisini birlikte kontrol etmeyi tercih ederim, çünkü MIME tipi bazen kolayca taklit edilebilir. Örneğin:
CODE
12
Bu kod bloğu, temel kontrolleri içeriyor.
CODE
1getimagesize()CODE
1falsePeki ya dosya adları? Kullanıcının yüklediği dosyanın adını olduğu gibi kaydetmek, ciddi riskler barındırır. Diyelim ki bir kullanıcı
CODE
1../../../../../etc/passwdCODE
1index.phpCODE
123
$newFileName = uniqid() . "." . $imageFileType; // örn: 5f7b4c9a3d2e1.jpg
$target_file = $target_dir . $newFileName;
Böylece hem çakışmaların önüne geçeriz hem de potansiyel yol geçişi (path traversal) saldırılarını engelleriz. Orijinal dosya adını kaydetmek isterseniz, onu veritabanına ayrı bir sütun olarak saklayabilirsiniz, ama asla sunucuda dosya adı olarak kullanmayın.
Bir de hedef dizinin yazma izinleri konusu var, yani
CODE
1uploads/CODE
1chmod 755CODE
1chmod 775CODE
1move_uploaded_file()CODE
1$_FILES['fileToUpload']['error']- : Dosya başarıyla yüklendi.CODE
1
UPLOAD_ERR_OK (0)
- : Yüklenen dosya boyutu,CODE
1
UPLOAD_ERR_INI_SIZE (1)'dekiCODE1
php.inideğerini aşıyor.CODE1
upload_max_filesize
- : Yüklenen dosya boyutu, HTML formundakiCODE
1
UPLOAD_ERR_FORM_SIZE (2)değerini aşıyor.CODE1
MAX_FILE_SIZE
- : Dosya kısmen yüklendi.CODE
1
UPLOAD_ERR_PARTIAL (3)
- : Hiçbir dosya yüklenmedi.CODE
1
UPLOAD_ERR_NO_FILE (4)
- : Geçici bir klasör eksik.CODE
1
UPLOAD_ERR_NO_TMP_DIR (6)
- : Diske yazılamadı.CODE
1
UPLOAD_ERR_CANT_WRITE (7)
- : Bir PHP uzantısı dosya yüklemesini durdurdu.CODE
1
UPLOAD_ERR_EXTENSION (8)
Dosyayı sunucuya taşıma işlemi için
CODE
1move_uploaded_file()CODE
1copy()CODE
1rename()CODE
123456
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
echo "Dosya " . htmlspecialchars($newFileName) . " başarıyla yüklendi.";
} else {
echo "Dosyayı yüklerken bir hata oluştu.";
}
Buradaki
CODE
1htmlspecialchars()Peki, yüklenen dosyalarla ilgili bilgileri veritabanına kaydetmeli miyiz? Kesinlikle evet! Dosyanın orijinal adı, yeni adı, boyutu, MIME tipi, yüklenme tarihi ve kim tarafından yüklendiği gibi bilgileri veritabanında tutmak, hem daha düzenli bir yapı sağlar hem de ileride raporlama veya dosya yönetimi açısından işimizi çok kolaylaştırır. Örneğin, bir kullanıcının yüklediği tüm resimleri listelemek istediğinizde, veritabanındaki bilgilere dayanarak bunu çok rahat yapabilirsiniz. Bir örnek tablo yapısı şöyle olabilir:
CODE
1234567891011
CREATE TABLE [icode]uploads[/icode] (
[icode]id[/icode] INT AUTO_INCREMENT PRIMARY KEY,
[icode]original_name[/icode] VARCHAR(255) NOT NULL,
[icode]stored_name[/icode] VARCHAR(255) NOT NULL,
[icode]file_path[/icode] VARCHAR(255) NOT NULL,
[icode]file_type[/icode] VARCHAR(100) NOT NULL,
[icode]file_size[/icode] INT NOT NULL,
[icode]uploaded_by[/icode] INT,
[icode]uploaded_at[/icode] DATETIME DEFAULT CURRENT_TIMESTAMP
);
Bu sayede, dosya sisteminde sadece benzersiz isimli dosyalar dururken, tüm meta verileri düzenli bir şekilde yönetmiş oluruz.
Çoklu dosya yükleme konusu da var elbette. Bu durumda HTML formunda
CODE
1input type="file"CODE
1multipleCODE
1nameCODE
1name="filesToUpload[]"CODE
1$_FILES['filesToUpload']Sonuç olarak, PHP ile dosya yükleme sistemi geliştirmek, sadece
CODE
1move_uploaded_file()Peki sizler, dosya yükleme sistemleri geliştirirken en çok hangi güvenlik önlemlerine dikkat ediyorsunuz? Ya da başınıza gelen en ilginç dosya yükleme hatası senaryosu neydi? Tecrübelerinizi ve görüşlerinizi merak ediyorum...