Laravel'de Güvenlik: Tehdit Modeli ve Savunma Katmanları
Laravel, güvenli varsayılanlar sunar; ancak çerçevenin sağladığı korumalar, geliştiricinin bu korumaları bilinçli kullanmasına bağlıdır. Bu rehber, orta seviye geliştiriciler için Laravel uygulamalarında en sık karşılaşılan güvenlik açıklarını ve bunlara karşı uygulanabilir savunma yöntemlerini ele alır.
Önkoşullar: Temel Laravel bilgisi (routing, Eloquent, Blade, middleware). Sürüm kapsamı: Laravel 10 ve 11. Sürüme özgü davranış farkları belirtilmiştir; sürüm numarası verilmediği sürece API'lerin bu iki sürümde benzer olduğu varsayılmıştır.
1. Kütle Atama (Mass Assignment)
Eloquent'in create ve update metotları, gelen istek verisini doğrudan modele aktarır. $fillable veya $guarded tanımlanmadığında saldırgan, formda olmayan bir alanı (örneğin is_admin) göndererek yetki yükseltebilir.
class User extends Model
{
protected $fillable = ['name', 'email', 'password'];
}
$fillable beyaz liste yaklaşımıdır; yalnızca izin verilen alanlar atanabilir. Alternatif olarak $guarded = [] kullanmak tüm alanları açığa çıkarır ve tehlikelidir. Beklenen davranış: listelenmeyen alanlar sessizce yok sayılır, hata fırlatılmaz. Bu nedenle $fillable listesini model değişikliklerinde güncel tutmak gerekir.
2. SQL Enjeksiyonu
Eloquent ve Query Builder, parametre bağlama (binding) kullandığı için normal sorgularda enjeksiyon riski düşüktür. Risk, ham sorgulara kullanıcı girdisinin doğrudan eklenmesiyle başlar.
// Riskli: kullanıcı girdisi doğrudan sorguya giriyor
$users = DB::select("SELECT * FROM users WHERE email = '$email'");
// Güvenli: parametre bağlama
$users = DB::select('SELECT * FROM users WHERE email = ?', [$email]);
Sütun veya tablo adı gibi bağlanamayan kısımlar için DB::getQueryGrammar()->wrap() yerine, girdiyi bir beyaz listeyle eşleştirmek daha güvenlidir:
$allowed = ['name', 'created_at'];
$column = in_array($request->input('sort'), $allowed, true)
? $request->input('sort')
: 'created_at';
$users = User::orderBy($column)->get();
orderBy gibi metotlar bağlama yapmadığı için kullanıcı girdisini doğrudan almamalıdır.
3. XSS ve Blade'de Çıktı Kaçışı
Blade'in {{ }} sözdizimi çıktıyı otomatik olarak HTML-encode eder. Risk, bu kaçışı devre dışı bırakan kullanımdan doğar:
{{-- Güvenli: otomatik kaçış --}}
{{ $comment }}
{{-- Riskli: kaçış yok, kullanıcı HTML/JS enjekte edebilir --}}
{!! $comment !!}
{!! !!} yalnızca içeriğin güvenilir olduğundan emin olduğunda kullanılmalıdır. Kullanıcıdan gelen zengin metin (örneğin WYSIWYG editör çıktısı) için HTML temizleyici bir kütüphaneyle içeriği filtrelemek gerekir; ham çıktı vermek XSS'e açık kapı bırakır.
4. CSRF Koruması
Laravel, web middleware grubundaki oturum tabanlı formlar için CSRF token doğrulaması yapar. Blade formlarında @csrf direktifi bulunmalıdır:
<form method="POST" action="/profile">
@csrf
...
</form>
Yaygın hata: SPA veya harici istemciler için API rotalarında CSRF'yi tamamen devre dışı bırakıp, oturum tabanlı kimlik doğrulamayı korumak. Bu durumda token yerine Authorization başlığıyla çalışan token tabanlı kimlik doğrulama (örneğin Sanctum) tercih edilmelidir. CSRF muafiyeti yalnızca VerifyCsrfToken içindeki $except dizisine eklenen rotalar için geçerlidir ve bu listeyi minimumda tutmak gerekir.
5. Kimlik Doğrulama ve Yetkilendirme
Kimlik doğrulama (kullanıcının kim olduğu) ile yetkilendirme (neye erişebileceği) ayrı katmanlardır. auth middleware'i yalnızca giriş yapılmış olmasını sağlar; kaynak sahipliğini kontrol etmez.
// Riskli: başka kullanıcının kaydı görüntülenebilir
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
// Güvenli: policy ile sahiplik kontrolü
public function show(Post $post)
{
$this->authorize('view', $post);
return view('posts.show', compact('post'));
}
Policy tanımı:
class PostPolicy
{
public function view(User $user, Post $post): bool
{
return $user->id === $post->user_id || $user->isAdmin();
}
}
Yaygın hata: Route model binding ile gelen nesneyi yetki kontrolü yapmadan döndürmek. Her kaynak erişiminde policy veya Gate kontrolü uygulanmalıdır.
6. Şifre Saklama ve Hash
Laravel, Hash facade'ı üzerinden bcrypt veya argon2 kullanır. Şifreler asla düz metin veya zayıf algoritmalarla (MD5, SHA1) saklanmamalıdır.
use Illuminate\Support\Facades\Hash;
$user->password = Hash::make($request->input('password'));
if (Hash::check($request->input('password'), $user->password)) {
// doğrulama başarılı
}
Beklenen davranış: Hash::make her çağrıda farklı bir tuz üretir; aynı şifre için farklı hash değerleri oluşur. Hash::check bu farkı otomatik yönetir. Şifre doğrulama kurallarında Password::min(8)->uncompromised() gibi kurallar kullanılabilir; ancak bu kuralın harici bir servise istek attığını ve ağ erişimi gerektirdiğini unutmamak gerekir.
7. Kütle Atama ve Yetkilendirmenin Birleşimi
Yetki yükseltme saldırılarının sık görülen yolu, kullanıcı güncelleme formunda role veya is_admin alanının gönderilmesidir. $fillable bunu engeller; ancak $request->all() ile doğrudan update çağrısı yapılıyorsa dikkat gerekir.
// Güvenli: yalnızca doğrulanmış alanlar
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email,' . $user->id,
]);
$user->update($validated);
validate metodu yalnızca kurallarda tanımlı alanları döndürür; bu, $fillable ile birlikte ikinci bir savunma katmanı sağlar.
8. Dosya Yükleme Güvenliği
Yüklenen dosyalar; uzantı, MIME türü ve içerik açısından doğrulanmalıdır.
$request->validate([
'avatar' => 'required|image|mimes:jpg,png|max:2048',
]);
$path = $request->file('avatar')->store('avatars', 'public');
Yaygın hata: Yüklenen dosyayı public dizinine doğrudan kaydetmek ve çalıştırılabilir uzantılara (.php, .phtml) izin vermek. Dosyalar storage altında tutulup, erişim kontrollü bir controller üzerinden sunulmalıdır. mimes kuralı uzantıya değil, dosya içeriğine göre tahmin edilen MIME türüne bakar; yine de sunucu tarafında ek doğrulama önerilir.
9. Ortam Değişkenleri ve Sırlar
.env dosyası sürüm kontrolüne eklenmemelidir; .gitignore içinde yer almalıdır. APP_KEY sızması, şifrelenmiş verilerin ve oturum çerezlerinin çözülebilmesi anlamına gelir. APP_DEBUG=true üretimde hata sayfalarında dosya yolları, sorgular ve ortam değişkenleri gibi bilgileri açığa çıkarır; üretimde APP_DEBUG=false olmalıdır.
10. Rate Limiting ve Brute Force
Giriş ve şifre sıfırlama gibi uç noktalar rate limiting ile korunmalıdır. Laravel'de throttle middleware'i veya RateLimiter facade'ı kullanılabilir:
Route::post('/login', [LoginController::class, 'store'])
->middleware('throttle:5,1');
Bu, dakikada 5 istek sınırı koyar. Sınır aşıldığında Laravel 429 yanıtı döner. Rate limiting tek başına yeterli değildir; başarısız giriş denemelerinin loglanması ve hesap kilitleme politikası ile birlikte düşünülmelidir.
11. Bağımlılık ve Sürüm Güncellemeleri
Laravel ekosistemindeki güvenlik açıkları çoğunlukla bağımlılıklarda ortaya çıkar. composer audit komutu, composer.lock içindeki bilinen açıkları raporlar. Düzenli güncelleme ve güvenlik duyurularının takibi, uygulama kodundan bağımsız bir savunma katmanıdır.
Yaygın Hataların Özeti
{!! !!}ile kullanıcı girdisini kaçışsız basmak → XSS.$guarded = []veya$fillabletanımlamamak → kütle atama ile yetki yükseltme.- Ham sorguya kullanıcı girdisi eklemek → SQL enjeksiyonu.
- Route model binding nesnesini yetki kontrolü yapmadan döndürmek → IDOR.
- Üretimde
APP_DEBUG=truebırakmak → bilgi sızıntısı. - API rotalarında CSRF'yi kapatıp oturum tabanlı kimlik doğrulamayı sürdürmek → CSRF açığı.
- Yüklenen dosyaları doğrulamadan
publicdizinine kaydetmek → uzaktan kod çalıştırma riski.
Sonuç
Laravel'in sağladığı korumalar (otomatik çıktı kaçışı, parametre bağlama, CSRF token, hash'leme) varsayılan olarak açıktır; ancak her biri, geliştiricinin onları devre dışı bırakmamasına veya atlamamasına bağlıdır. Güvenlik tek bir özellik değil, doğrulama, yetkilendirme, çıktı kodlama ve bağımlılık yönetiminin bir arada uygulandığı bir süreçtir. Yeni bir özellik eklerken "bu veriyi kim gönderebilir, kim görebilir, nasıl saklanır ve nasıl gösterilir" sorularını sormak, çoğu açığın oluşmasını baştan engeller.