Laravel jest szybki, potężny i elegancki — ale czy Twoje aplikacje są bezpieczne?
Niezależnie czy jesteś juniorem, czy samodzielnym twórcą wdrażającym aplikacje Laravel + Inertia.js, zrozumienie OWASP Top 10 i innych realnych zagrożeń bezpieczeństwa jest kluczowe.
Ten przewodnik przeprowadzi Cię przez:
- ✅ OWASP Top 10 — z prawdziwymi przykładami dla Laravel + React
- ✅ Jak naprawić każdy problem (Gate, Policy, Middleware, Fortify, Sanctum itd.)
- ✅ Dodatkowe zagrożenia spoza OWASP (równie niebezpieczne)
- ✅ Ostateczną Checklistę Bezpieczeństwa Produkcyjnego Laravel
📚 Spis treści
- Zabezpiecz swoją aplikację Laravel: OWASP Top 10 + Więcej
- 📚 Spis treści
- Czym jest OWASP?
- OWASP Top 10 z przykładami dla Laravel
- 1. Naruszenie Kontroli Dostępu (A01)
- 2. Błędy Kryptograficzne (A02)
- 3. Wstrzykiwanie (A03)
- 4. Niebezpieczny Projekt (A04)
- 5. Błędna Konfiguracja Bezpieczeństwa (A05)
- 6. Podatne Komponenty (A06)
- 7. Błędy Identyfikacji i Uwierzytelniania (A07)
- 8. Błędy Integralności Oprogramowania i Danych (A08)
- 9. Błędy Logowania i Monitoringu Bezpieczeństwa (A09)
- 10. SSRF — Fałszowanie Żądań po Stronie Serwera (A10)
- Poza OWASP: Dodatkowe Zagrożenia
Czym jest OWASP?
Open Web Application Security Project (OWASP) to fundacja non-profit, której celem jest poprawa bezpieczeństwa oprogramowania. Ich flagowy projekt — OWASP Top 10 — to lista 10 najpoważniejszych zagrożeń bezpieczeństwa aplikacji webowych.
Laravel daje Ci świetne narzędzia do zapobiegania większości z nich, ale musisz ich używać poprawnie.
OWASP Top 10 z przykładami dla Laravel
1. Naruszenie Kontroli Dostępu (A01)
Problem: Użytkownicy uzyskują dostęp do danych lub funkcji, do których nie powinni mieć dostępu.
// ❌ Trasa admina dostępna dla każdego zalogowanego
Route::get('/admin/users', [UserController::class, 'index']);
// ✅ Użyj Policy
$this->authorize('viewAny', User::class);
// ✅ Przykład Policy
public function viewAny(User $user)
{
return $user->is_admin;
}
// Lub Gate:
Gate::define('access-admin-panel', fn(User $user) => $user->is_admin);
Wskazówka: Dla bardziej zaawansowanej lub wielokrotnie wykorzystywanej logiki dostępu najlepiej stworzyć własny middleware. W Laravel 12 rejestracja middleware została przeniesiona z app/Http/Kernel.php do bootstrap/app.php.
Jak utworzyć i zarejestrować własny middleware w Laravel 12:
- Utwórz middleware:
php artisan make:middleware EnsureUserIsAdmin
- Zarejestruj alias middleware w pliku
bootstrap/app.php:
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(...)
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
]);
})
// ...
->create();
- Użyj aliasu middleware w trasach lub kontrolerach:
// W routes/web.php
Route::middleware(['admin'])->group(function () {
Route::get('/admin/users', [UserController::class, 'index']);
});
// Lub na pojedynczej trasie
Route::get('/admin/dashboard', [DashboardController::class, 'index'])->middleware('admin');
// Lub w konstruktorze kontrolera
public function __construct()
{
$this->middleware('admin');
}
Uwaga: Jeśli aktualizujesz projekt z Laravel 10 lub starszego, pamiętaj, że rejestracja middleware odbywa się teraz w
bootstrap/app.php, a nie wapp/Http/Kernel.php.
2. Błędy Kryptograficzne (A02)
Problem: Wrażliwe dane, takie jak hasła czy tokeny, są przechowywane w niezaszyfrowanej formie.
// ❌ Nigdy nie przechowuj surowego hasła
User::create(['password' => $request->password]);
// ✅ Zawsze używaj hash()
use Illuminate\Support\Facades\Hash;
User::create([
'password' => Hash::make($request->password),
]);
// 🔐 Użyj też Crypt::encryptString() dla tokenów lub sekretów API
use Illuminate\Support\Facades\Crypt;
$encrypted = Crypt::encryptString($token);
Lepsza praktyka:
Zamiast ręcznie szyfrować i odszyfrowywać dane, możesz użyć Eloquent Attribute Casting. W Laravelu 11 i nowszych używaj metody casts() zamiast właściwości $casts:
// W modelu User.php
protected function casts(): array
{
return [
'api_token' => 'encrypted',
'settings' => 'encrypted:array', // dla tablicy
];
}
Dzięki temu Laravel automatycznie zaszyfruje i odszyfruje pole przy zapisie/odczycie.
Uwaga: Od Laravel 11+ casty atrybutów definiuje się przez metodę
casts(), a nie przez właściwość$casts. To daje większą elastyczność i czytelność modeli. Zobacz oficjalną dokumentację po więcej przykładów i zaawansowane użycie.
Co warto haszować lub szyfrować?
- Hasła użytkowników (haszować — zawsze przez Hash::make())
- Tokeny API, refresh tokeny (szyfrować — np. przez cast 'encrypted')
- Klucze API, sekrety integracji
- Wrażliwe dane użytkownika (np. PESEL, NIP, adresy, jeśli wymagają dodatkowej ochrony)
- Dane konfiguracyjne, które nie powinny być jawne w bazie
Pamiętaj:
- Haszowanie (np. haseł) jest jednokierunkowe — nie można odzyskać oryginału.
- Szyfrowanie (np. tokenów, sekretów) jest dwukierunkowe — można odszyfrować, gdy jest taka potrzeba.
3. Wstrzykiwanie (A03)
Problem: Dane wejściowe użytkownika modyfikują zapytania SQL, polecenia powłoki lub inne komendy.
// ❌ Surowy SQL podatny na wstrzyknięcia
DB::select("SELECT * FROM users WHERE email = '{$email}'");
// ✅ Używaj Query Buildera lub Eloquent
User::where('email', $email)->first();
Wskazówki:
- Zawsze waliduj dane wejściowe przez Form Requesty.
- Używaj zapytań parametryzowanych.
- Escapuj wyjście w Blade przez
{{ }}(nigdy nie używaj{!! !!}bez sanitizacji).
4. Niebezpieczny Projekt (A04)
Problem: Brak zabezpieczeń już na etapie projektowania.
Przykłady:
- Brak rate limiting
- Brak weryfikacji e-mail
- Brak MFA przy usuwaniu konta
// ✅ Ogranicz logowanie
Route::post('/login', [AuthController::class, 'login'])->middleware('throttle:5,1');
- Użyj Laravel Fortify do weryfikacji e-mail i 2FA.
- Projektuj zgodnie z zasadą najmniejszych uprawnień i defense in depth.
5. Błędna Konfiguracja Bezpieczeństwa (A05)
Problem: Niebezpieczne ustawienia w środowisku produkcyjnym.
APP_DEBUG=trueujawnia stack trace.envdostępny, jeśli /public nie jest web rootem
✅ Rozwiązania:
APP_DEBUG=falsew .env- Użyj
php artisan config:cache - Udostępniaj tylko
/publicprzez Nginx lub Apache - Ustaw odpowiednie uprawnienia na storage i .env
6. Podatne Komponenty (A06)
Problem: Używanie nieaktualnych lub podatnych zależności.
✅ Regularnie audytuj zależności:
composer audit
npm audit fix
- Pinuj wersje w composer.json i package.json.
- Monitoruj wydania Laravel, Inertia, Sanctum itd.
- Usuwaj nieużywane paczki.
7. Błędy Identyfikacji i Uwierzytelniania (A07)
Problem: Błędna logika uwierzytelniania.
// ❌ Hasło nie jest haszowane
User::create(['password' => $request->password]);
// ✅ Używaj Fortify do logowania i resetu haseł.
- Wymuszaj silne hasła i polityki haseł.
- Używaj wbudowanego scaffoldu uwierzytelniania Laravel.
- Blokuj konto po wielu nieudanych próbach logowania.
8. Błędy Integralności Oprogramowania i Danych (A08)
Problem: Zmodyfikowane pliki, niepodpisane zasoby, przerwane CI/CD.
<!-- ❌ Niezweryfikowany skrypt -->
<script src="https://cdn.example.com/react.js"></script>
<!-- ✅ Użyj integrity hash -->
<script src="..." integrity="sha384-..." crossorigin="anonymous"></script>
- Używaj tylko zaufanych, przypiętych paczek.
- Podpisuj artefakty wdrożeniowe, jeśli to możliwe.
- Używaj podpisanych URLi Laravel dla wrażliwych akcji.
9. Błędy Logowania i Monitoringu Bezpieczeństwa (A09)
Problem: Brak możliwości wykrycia lub prześledzenia podejrzanej aktywności.
✅ Loguj nieudane logowania, resety haseł, podejrzane zachowania:
Log::warning('Login failed', [
'email' => $request->email,
'ip' => $request->ip(),
]);
- Używaj Laravel Telescope lub narzędzi zewnętrznych (Sentry, ELK).
- Ustaw alerty na podejrzaną aktywność.
- Regularnie przeglądaj logi.
10. SSRF — Fałszowanie Żądań po Stronie Serwera (A10)
Problem: Aplikacja wykonuje żądania do adresów podanych przez użytkownika.
// ❌ Użytkownik podaje dowolny URL
Http::get($request->input('url'));
// ✅ Whitelistuj zaufane domeny:
$request->validate([
'url' => 'required|url|regex:/^https:\/\/api\.mojadomena\.pl/',
]);
Http::get($request->input('url'))->throw();
- Nigdy nie pobieraj dowolnych URLi od użytkownika.
- Używaj allow-list i ścisłej walidacji.
Poza OWASP: Dodatkowe Zagrożenia
⚠️ 1. Masowe Przypisywanie (Mass Assignment)
Problem: Atakujący mogą ustawić dowolny atrybut modelu, jeśli nie ograniczysz pól dozwolonych do masowego przypisania.
// ❌ Niebezpieczne: pozwala na ustawienie wszystkich pól z requesta
User::create($request->all());
// ✅ Użyj $fillable, by jawnie określić dozwolone pola
class User extends Model {
protected $fillable = ['name', 'email', 'password'];
}
// Lub $guarded, by zablokować wybrane pola
class User extends Model {
protected $guarded = ['is_admin', 'role'];
}
// ✅ Bezpieczniej: przekazuj tylko dozwolone pola
User::create($request->only(['name', 'email', 'password']));
Dobre praktyki:
- Zawsze definiuj
$fillablelub$guardedw modelach. - Nigdy nie używaj
$request->all()do masowego przypisania. - Waliduj dane wejściowe przez Form Requesty.
🔎 2. Cross-Site Scripting (XSS)
Problem: Dane wejściowe użytkownika są renderowane jako HTML/JS, co pozwala na wstrzyknięcie skryptów.
W Blade:
// ❌ Podatne: wyświetla surowy HTML
{!! $userInput !!}
// ✅ Bezpieczne: escapuje wyjście
{{ $userInput }}
W Inertia/React:
// ❌ Podatne: użycie dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userInput }} />
// ✅ Bezpieczne: renderuj jako tekst lub sanitizuj
<div>{userInput}</div>
// lub użyj DOMPurify jeśli musisz renderować HTML
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />
Dobre praktyki:
- Nigdy nie ufaj danym wejściowym w HTML.
- Zawsze escapuj wyjście w Blade (
{{ }}) i sanitizuj w React jeśli renderujesz HTML. - Używaj bibliotek typu DOMPurify do sanitizacji.
🌐 3. Błędna Konfiguracja CORS
Problem: Zezwolenie na wszystkie domeny (*) naraża API na ataki cross-origin.
Przykład (config/cors.php):
// ❌ Niebezpieczne
'allowed_origins' => ['*'],
// ✅ Tylko zaufane domeny
'allowed_origins' => ['https://twojadomena.pl', 'https://admin.twojadomena.pl'],
Dobre praktyki:
- Nigdy nie używaj
*dla API wymagających autoryzacji. - Ograniczaj CORS tylko do zaufanych domen.
🕓 4. Błędna Konfiguracja Sesji
Problem: Niezabezpieczone ciasteczka sesyjne mogą zostać przechwycone lub zmanipulowane.
Przykład (config/session.php):
// ✅ Bezpieczne ustawienia sesji
'cookie_secure' => env('SESSION_SECURE_COOKIE', true),
'http_only' => true,
'same_site' => 'lax', // lub 'strict' dla większego bezpieczeństwa
Dobre praktyki:
- Zawsze używaj HTTPS w produkcji.
- Ustawiaj ciasteczka jako
secure,http_onlyisame_site. - Rotuj identyfikatory sesji po zalogowaniu.
🗂️ 5. Otwarte Przekierowania
Problem: Przekierowanie do adresu z danych wejściowych użytkownika może umożliwić phishing.
// ❌ Podatne
return redirect($request->input('next'));
// ✅ Tylko wewnętrzne adresy
$next = $request->input('next');
if ($next && Str::startsWith($next, '/')) {
return redirect($next);
}
return redirect('/dashboard');
Dobre praktyki:
- Nigdy nie przekierowuj do dowolnych adresów z danych wejściowych.
- Zawsze waliduj lub sanitizuj adresy przekierowań.
🗄️ 6. Niebezpieczne Uploady Plików
Problem: Użytkownicy mogą uploadować niebezpieczne pliki (np. skrypty PHP, malware).
// ✅ Waliduj typ i rozmiar pliku
$request->validate([
'avatar' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048',
]);
// ✅ Przechowuj poza katalogiem publicznym jeśli to możliwe
$path = $request->file('avatar')->store('avatars', 'private');
Dobre praktyki:
- Zawsze waliduj typ, rozmiar i zawartość pliku.
- Przechowuj uploady poza katalogiem publicznym jeśli to możliwe.
- Nigdy nie wykonuj ani nie serwuj uploadowanych plików jako kod.
🛑 7. Ujawnienie .env
Problem: Jeśli web root jest źle ustawiony, plik .env może być dostępny z internetu, co grozi wyciekiem sekretów.
Dobre praktyki:
- Zawsze ustawiaj web root na katalog
public/. - Nigdy nie commituj plików
.envdo repozytorium. - Używaj zmiennych środowiskowych do sekretów w produkcji.
Śledź mnie na LinkedIn, aby otrzymywać więcej wskazówek o Laravel i DevOps!
Czy chciałbyś dowiedzieć się więcej o bezpieczeństwie aplikacji? Daj znać w komentarzach poniżej!