· Admin

Laravel Güvenlik Rehberi: XSS, CSRF, SQL Injection ve Daha Fazlası

Guvenlik konusu, "benim projeme kim saldirsin ki" diyerek gecistirilecek bir sey degil. Otomatize botlar her gun milyonlarca siteyi tarar ve acik arar. Laravel guvenlik konusunda guclu varsayilan korumalara sahip ama bunlari bilmeden, yanlis kullanarak veya devre disi birakarak projelerinizi riske atabilirsiniz. Bu yazida OWASP Top 10'dan baslayarak, her projemde uyguladigim guvenlik pratiklerini paylasiyorum.

OWASP Top 10 ve Laravel

OWASP (Open Web Application Security Project) her yil en yaygin web guvenlik acikliklari listesini yayinlar. Laravel'in bu listedeki tehditlerin cogu icin varsayilan korumalari var. Ama "varsayilan" demek "garanti" demek degil. Bakalim neler yapmaliyiz.

XSS (Cross-Site Scripting) Korumasi

XSS, saldirganin sayfaniza zararli JavaScript enjekte etmesidir. Kullanici verisi gosterilen her yer potansiyel risk tasiyor.

Laravel Blade'de {{ }} kullandiginizda otomatik HTML escaping yapilir:

{{-- GUVENLI: htmlspecialchars() uygulanir --}}
<p>{{ $user->name }}</p>
<p>{{ $post->title }}</p>

{{-- TEHLIKELI: Ham HTML render edilir --}}
<p>{!! $post->content !!}</p>

{!! !!} kullanmaniz gereken durumlar olacak, ornegin WYSIWYG editor iceriginde. Bu durumda HTML'i mutlaka temizleyin:

// HTMLPurifier ile temizleme
use HTMLPurifier;
use HTMLPurifier_Config;

public function store(Request $request)
{
    $config = HTMLPurifier_Config::createDefault();
    $config->set('HTML.Allowed', 'p,br,strong,em,ul,ol,li,a[href],h2,h3,h4,pre,code,blockquote');
    $purifier = new HTMLPurifier($config);

    $post->content = $purifier->purify($request->input('content'));
    $post->save();
}

JavaScript context'inde de dikkatli olun:

{{-- TEHLIKELI --}}
<script>
    var userData = {!! json_encode($user) !!};
</script>

{{-- GUVENLI --}}
<script>
    var userData = @json($user);
</script>

Blade'in @json directive'i otomatik escaping yapar. Projelerimde JavaScript'e veri aktarirken her zaman @json kullaniyorum.

CSRF (Cross-Site Request Forgery)

CSRF, kullanicinin haberi olmadan baska bir siteden sizin sitenize istek gonderilmesidir. Laravel bu konuda guclu bir koruma saglar.

Her form'a CSRF token eklemek zorunludur:

<form method="POST" action="/posts">
    @csrf
    <input type="text" name="title">
    <button type="submit">Kaydet</button>
</form>

SPA (Single Page Application) kullaniyorsaniz, Sanctum ile CSRF cookie otomatik yonetilir:

// Axios ile SPA'da CSRF
// Oncelikle CSRF cookie'sini al
await axios.get('/sanctum/csrf-cookie');

// Sonra istekleri gonder (cookie otomatik gonderilir)
await axios.post('/api/posts', { title: 'Yeni Post' });

Belirli route'lari CSRF'den muaf tutmaniz gerekiyorsa (ornegin webhook endpoint'leri), bunu bootstrap/app.php icerisinde tanimlayin:

->withMiddleware(function (Middleware $middleware) {
    $middleware->validateCsrfTokens(except: [
        'webhook/*',
        'api/payments/callback',
    ]);
})

SQL Injection Korumasi

SQL injection, kullanici girdisinin dogrudan SQL sorgusuna eklenmesiyle olusur. Eloquent ORM varsayilan olarak parameterized query kullanir ve bu saldiriyi onler.

// GUVENLI: Eloquent parameterized query
$user = User::where('email', $request->email)->first();

// GUVENLI: Query Builder parameterized
$users = DB::table('users')
    ->where('role', $request->role)
    ->get();

// TEHLIKELI: Raw query, kullanici girdisi direkt
$users = DB::select("SELECT * FROM users WHERE role = '" . $request->role . "'");

// GUVENLI: Raw query ama parameterized
$users = DB::select("SELECT * FROM users WHERE role = ?", [$request->role]);

// GUVENLI: Raw expression ile named binding
$posts = DB::table('posts')
    ->whereRaw('MATCH(title, content) AGAINST(? IN BOOLEAN MODE)', [$searchTerm])
    ->get();

Kural basit: Eloquent veya Query Builder kullanin, raw query'den kacinin. Raw query kullanmaniz gerekiyorsa mutlaka parameter binding kullanin. ? placeholder veya :name named binding ile.

Mass Assignment Korumasi

Laravel'de model uzerinden toplu atama yapildiginda, hangi alanlarin doldurulabilecegi kontrol edilmelidir:

class Post extends Model
{
    // YONTEM 1: Izin verilen alanlar (TAVSIYE)
    protected $fillable = [
        'title', 'slug', 'content', 'excerpt',
        'featured_image', 'status', 'category_id'
    ];

    // YONTEM 2: Yasaklanan alanlar
    // protected $guarded = ['id', 'is_admin'];

    // ASLA: Tum alanlari acik birakma
    // protected $guarded = [];
}

$fillable kullanmak daha guvenlidir cunku acikca izin verilen alanlari belirtirsiniz. $guarded = [] kullanmak admin flag'leri veya hassas alanlarin degistirilmesine yol acabilir.

// GUVENLI: Sadece fillable alanlar atanir
$post = Post::create($request->validated());

// EKSTRA GUVENLI: Form request validation ile
class StorePostRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'title' => 'required|string|max:255',
            'content' => 'required|string',
            'status' => 'required|in:draft,published',
            'category_id' => 'required|exists:categories,id',
        ];
    }
}

Authentication ve Password Guvenligi

Her projemde AppServiceProvider icerisinde password kurallarini tanimliyorum. Bu benim icin vazgecilmez bir standart:

// app/Providers/AppServiceProvider.php
use Illuminate\Validation\Rules\Password;

public function boot(): void
{
    Password::defaults(function () {
        return Password::min(8)
            ->letters()
            ->numbers()
            ->uncompromised();
    });
}

uncompromised() metodu Have I Been Pwned API'sini kullanarak sifrenin daha once bir veri ihlaline karismis olup olmadigini kontrol eder. Kullanicilarin "123456" gibi sifreler secmesini engeller.

Kullanim:

$request->validate([
    'password' => ['required', 'confirmed', Password::defaults()],
]);

Rate Limiting

Brute force saldirilarini engellemek icin rate limiting sart:

// bootstrap/app.php veya RouteServiceProvider
use Illuminate\Cache\RateLimiting\Limit;

RateLimiter::for('login', function (Request $request) {
    return Limit::perMinute(5)->by($request->ip());
});

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
// Route taniminda
Route::post('/login', [AuthController::class, 'login'])
    ->middleware('throttle:login');

CORS ve Header Guvenligi

CORS yapilandirmasini gereksiz yere acik birakmayin:

// config/cors.php
return [
    'paths' => ['api/*', 'sanctum/csrf-cookie'],
    'allowed_origins' => ['https://yourdomain.com'],  // '*' kullanmayin
    'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE'],
    'allowed_headers' => ['Content-Type', 'Authorization', 'X-Requested-With'],
    'max_age' => 86400,
];

Guvenlik header'lari icin middleware olusturun:

class SecurityHeaders
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('X-Frame-Options', 'SAMEORIGIN');
        $response->headers->set('X-XSS-Protection', '1; mode=block');
        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
        $response->headers->set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');

        return $response;
    }
}

File Upload Guvenligi

Dosya yuklemeleri buyuk bir saldiri vektoru olabilir:

public function upload(Request $request)
{
    $request->validate([
        'file' => [
            'required',
            'file',
            'max:5120', // 5MB
            'mimes:jpg,jpeg,png,gif,webp,pdf',
        ],
    ]);

    // Orijinal dosya adini kullanmayin
    $path = $request->file('file')->store('uploads', 'public');

    // Dosya adini hashleyin
    $filename = Str::uuid() . '.' . $request->file('file')->extension();
    $path = $request->file('file')->storeAs('uploads', $filename, 'public');

    return $path;
}

Onemli noktalar: Orijinal dosya adini asla kullanmayin. MIME tipini sunucu tarafinda dogrulayin (client tarafindaki extension yetmez). Upload dizinini web root disinda tutun veya .htaccess / Nginx config ile PHP calistirmayi engelleyin.

Her Projemde Uyguladigim Guvenlik Checklist'i

Bu liste her yeni projede ilk yaptigim seyler:

  • .env dosyasi .gitignore'da mi? APP_DEBUG=false production'da mi?
  • Password::defaults() ile uncompromised() tanimli mi?
  • CSRF token tum formlarda var mi?
  • Mass assignment korumasi ($fillable) tum modellerde tanimli mi?
  • Rate limiting login ve API route'larinda aktif mi?
  • File upload'larda MIME validation ve dosya adi hashleme var mi?
  • Raw query kullanildiysa parameterized mi?
  • Guvenlik header'lari middleware'i aktif mi?
  • CORS sadece gerekli origin'lere acik mi?
  • SSL/TLS (HTTPS) zorunlu mu?
  • APP_KEY benzersiz ve guclu mu?
  • Hassas veriler (API key, token) .env'de mi, kodda degil mi?

Sonuc

Laravel guvenlik konusunda harika varsayilan korumalara sahip ama bu korumalari bilmeden, yanlis kullanarak veya devre disi birakarak kendinizi riske atabilirsiniz. Bu checklist'i her projemde uyguluyorum ve su ana kadar ciddi bir guvenlik sorunu yasamadim. Guvenlik bir ozellik degil, bir aliskanliktir. Bu aliskanliklarla projeleriniz cok daha saglam olur.