40 de verificări de securitate pentru aplicația ta Laravel
Ai învățat despre semnăturile malware, tehnicile de evaziune și CVE-uri. Acum e timpul să auditezi propria aplicație.
Acest capitol oferă 40 de verificări de securitate acționabile organizate pe categorii. Parcurge acest checklist pe fiecare proiect Laravel - fie că e o construcție nouă sau cod moștenit pe care l-ai preluat.
Cum să Folosești Acest Checklist
Fiecare verificare include:
- Ce să cauți - Problema de securitate
- Cum să verifici - Comenzi sau cod pentru verificare
- Cum să repari - Pași de remediere
- Severitate - CRITICĂ / RIDICATĂ / MEDIE / SCĂZUTĂ
Abordare recomandată:
- Rulează mai întâi toate verificările CRITICE
- Adresează problemele cu severitate RIDICATĂ
- Programează MEDIE/SCĂZUTĂ pentru sprint-ul următor
- Re-rulează checklist-ul după lansări majore
Categoria 1: Configurație (8 Verificări)
Verificarea #1: Mod Debug Dezactivat
Severitate: CRITICĂ
Modul debug expune variabile de mediu, credențiale bază de date și activează vulnerabilități XSS.
# Verifică fișierul .env
grep "APP_DEBUG" .env
# Trebuie să fie: APP_DEBUG=false
// Sau în tinker
config('app.debug'); // Trebuie să fie false în producție
Remediere: Setează APP_DEBUG=false în .env de producție
Verificarea #2: Environment Setat pe Production
Severitate: CRITICĂ
grep "APP_ENV" .env
# Trebuie să fie: APP_ENV=production
De ce contează: Multe pachete se comportă diferit în local vs production. Funcționalitățile de securitate pot fi dezactivate în medii non-producție.
Verificarea #3: APP_KEY Setat Corect
Severitate: CRITICĂ
grep "APP_KEY" .env
# Trebuie să înceapă cu: APP_KEY=base64:
# Trebuie să fie 32 bytes (44 caractere în base64)
// Verifică lungimea cheii
strlen(base64_decode(substr(config('app.key'), 7))); // Trebuie să fie 32
Remediere: php artisan key:generate
Nu Partaja Niciodată APP_KEY
Dacă APP_KEY este scurs, atacatorii pot decripta toate datele criptate, falsifica sesiuni și executa RCE via deserializare.
Verificarea #4: .env Nu Este în Git
Severitate: CRITICĂ
# Verifică dacă .env este urmărit
git ls-files | grep "^\.env$"
# Ar trebui să nu returneze nimic
# Verifică .gitignore
grep "\.env" .gitignore
# Ar trebui să includă .env
Remediere: Adaugă în .gitignore:
.env
.env.backup
.env.*.local
Verificarea #5: Debugbar Dezactivat
Severitate: RIDICATĂ
# Verifică dacă e instalat
composer show | grep debugbar
# Dacă e instalat, verifică config
grep "DEBUGBAR_ENABLED" .env
# Trebuie să fie: DEBUGBAR_ENABLED=false
Remediere: Instalează doar în require-dev și dezactivează explicit:
composer require barryvdh/laravel-debugbar --dev
Verificarea #6: Telescope Securizat
Severitate: RIDICATĂ
// app/Providers/TelescopeServiceProvider.php
protected function gate()
{
Gate::define('viewTelescope', function ($user) {
// Trebuie să restricționezi accesul!
return in_array($user->email, [
'admin@yourdomain.com',
]);
});
}
Remediere: Definește întotdeauna un Gate care restricționează accesul la Telescope.
Verificarea #7: register_argc_argv Dezactivat
Severitate: RIDICATĂ
php -i | grep register_argc_argv
# Trebuie să fie: Off
Remediere: În php.ini:
register_argc_argv = Off
Verificarea #8: Afișare Erori Dezactivată
Severitate: MEDIE
php -i | grep display_errors
# Trebuie să fie: Off
// Verifică și config-ul Laravel
config('app.debug'); // false
Categoria 2: Autentificare (5 Verificări)
Verificarea #9: Algoritm Hashing Parole
Severitate: RIDICATĂ
// config/hashing.php
'driver' => 'bcrypt', // sau 'argon2id'
// Verifică costul bcrypt
'bcrypt' => [
'rounds' => 12, // Minim 10, recomandat 12
],
Verifică parolele existente:
// Parolele ar trebui să înceapă cu $2y$ (bcrypt) sau $argon2id$
User::first()->password;
Verificarea #10: Securitate Sesiune
Severitate: RIDICATĂ
// config/session.php
'secure' => true, // Cookie-uri doar peste HTTPS
'http_only' => true, // Fără acces JavaScript
'same_site' => 'lax', // Sau 'strict' pentru mai multă securitate
'lifetime' => 120, // Timeout rezonabil (minute)
# Verifică .env
grep "SESSION_DRIVER" .env
# Evită: file (folosește database, redis sau memcached)
Verificarea #11: Throttling Login
Severitate: RIDICATĂ
// Verifică dacă middleware-ul throttle e aplicat la login
// routes/web.php sau LoginController
Route::post('/login', [LoginController::class, 'login'])
->middleware('throttle:5,1'); // 5 încercări pe minut
Cu Breeze/Jetstream:
// App folosește RateLimiter - verifică că nu e dezactivat
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->email);
});
Verificarea #12: Expirare Token Reset Parolă
Severitate: MEDIE
// config/auth.php
'passwords' => [
'users' => [
'expire' => 60, // Minute - nu seta prea mare
],
],
Verificarea #13: Implementare MFA
Severitate: MEDIE
# Verifică dacă pachetul MFA e instalat
composer show | grep -E "two-factor|2fa|totp"
Recomandat: Implementează MFA cel puțin pentru conturile admin.
Categoria 3: Autorizare (4 Verificări)
Verificarea #14: Gates Returnează Boolean Explicit
Severitate: RIDICATĂ
// VULNERABIL - return implicit
Gate::define('admin', function ($user) {
if ($user->is_admin) {
return true;
}
// Lipsește return false!
});
// SECURIZAT - return explicit
Gate::define('admin', function ($user) {
return $user->is_admin === true;
});
Auditează toate Gate-urile:
grep -rn "Gate::define" app/
Verificarea #15: Policies Înregistrate
Severitate: MEDIE
// app/Providers/AuthServiceProvider.php
protected $policies = [
Post::class => PostPolicy::class,
User::class => UserPolicy::class,
// Toate modelele cu autorizare ar trebui să fie aici
];
Verificarea #16: Middleware Aplicat pe Rute
Severitate: RIDICATĂ
# Listează toate rutele și middleware-ul lor
php artisan route:list --columns=uri,middleware
Verifică rutele admin neprotejate:
// Toate rutele admin ar trebui să aibă auth + autorizare
Route::middleware(['auth', 'can:admin'])->prefix('admin')->group(...);
Verificarea #17: Rute API Protejate
Severitate: RIDICATĂ
// routes/api.php - verifică middleware-ul auth
Route::middleware('auth:sanctum')->group(function () {
// Rute API protejate
});
// Rutele publice ar trebui să fie intenționate și limitate
Categoria 4: Validare Input (5 Verificări)
Verificarea #18: Utilizare Form Requests
Severitate: RIDICATĂ
# Verifică dacă există Form Requests
ls app/Http/Requests/
# Controller-ele ar trebui să folosească Form Requests, nu validare inline
grep -rn "validate(\$request" app/Http/Controllers/
# Minimizează validarea inline
Best practice:
public function store(StorePostRequest $request) // Form Request
{
Post::create($request->validated()); // Doar date validate
}
Verificarea #19: Protecție Mass Assignment
Severitate: CRITICĂ
// Modelele ar trebui să definească $fillable sau $guarded
class User extends Model
{
protected $fillable = ['name', 'email']; // Abordare whitelist
// SAU
protected $guarded = ['id', 'is_admin']; // Abordare blacklist
// NU FOLOSI NICIODATĂ:
// protected $guarded = []; // Permite TOATE câmpurile!
}
Auditează toate modelele:
grep -rn "guarded = \[\]" app/Models/
# Ar trebui să nu returneze nimic!
Verificarea #20: Prevenire SQL Injection
Severitate: CRITICĂ
# Caută query-uri raw cu variabile
grep -rn "DB::raw\|whereRaw\|selectRaw" app/
Vulnerabil:
DB::select("SELECT * FROM users WHERE id = $id"); // VULNERABIL
Securizat:
DB::select("SELECT * FROM users WHERE id = ?", [$id]); // Parametrizat
User::where('id', $id)->first(); // Eloquent (sigur)
Verificarea #21: Validare Upload Fișiere
Severitate: CRITICĂ
// Validează tipul fișierului după conținut, nu doar extensie
$request->validate([
'document' => [
'required',
'file',
'mimes:pdf,doc,docx', // Validare tip MIME
'max:10240', // Limită dimensiune (KB)
],
]);
// Stochează în afara directorului public
$path = $request->file('document')->store('documents'); // storage/app/documents
Nu Te Încrede Niciodată în Extensiile Fișierelor
Atacatorii redenumesc shell.php în shell.pdf. Validează întotdeauna tipul
MIME și stochează upload-urile în afara rădăcinii web.
Verificarea #22: Prevenire XSS în Output
Severitate: RIDICATĂ
# Caută output ne-escapat
grep -rn "{!!" resources/views/
Vulnerabil:
{!! $userInput !!} // Randează HTML - risc XSS!
Securizat:
{{ $userInput }} // Escapat implicit
{!! clean($html) !!} // Dacă e nevoie de HTML, sanitizează întâi
Categoria 5: Securitate Bază de Date (4 Verificări)
Verificarea #23: Credențiale Bază de Date Securizate
Severitate: CRITICĂ
# Verifică config-ul bazei de date
grep "DB_PASSWORD" .env
# Ar trebui să fie parolă puternică, nu 'password' sau 'secret'
Nu hardcoda niciodată:
// config/database.php
'password' => env('DB_PASSWORD'), // Bine - din environment
'password' => 'mypassword', // Rău - hardcodat
Verificarea #24: Criptare pentru Date Sensibile
Severitate: RIDICATĂ
// Datele sensibile ar trebui să folosească criptarea Laravel
use Illuminate\Support\Facades\Crypt;
// Stocare
$encrypted = Crypt::encryptString($cnp);
// Recuperare
$cnp = Crypt::decryptString($encrypted);
// Sau folosește Eloquent casts
protected $casts = [
'cnp' => 'encrypted',
];
Verificarea #25: Backup-uri Bază de Date Criptate
Severitate: RIDICATĂ
# Verifică configurația backup
# Dacă folosești spatie/laravel-backup
grep -A5 "encryption" config/backup.php
Asigură-te că backup-urile sunt:
- Criptate la stocare
- Stocate în afara serverului
- Testate regulat
Verificarea #26: Fără Date Sensibile în Loguri
Severitate: RIDICATĂ
// config/logging.php - verifică ce se loghează
// Asigură-te că parole, token-uri, etc. nu sunt loggate
// În modele, ascunde atributele sensibile
protected $hidden = ['password', 'remember_token', 'api_key'];
# Caută tipare de date sensibile în loguri
grep -rn "password\|api_key\|secret" storage/logs/
Categoria 6: Securitate API (4 Verificări)
Verificarea #27: Rate Limiting Configurat
Severitate: RIDICATĂ
// app/Providers/RouteServiceProvider.php
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
// routes/api.php
Route::middleware(['throttle:api'])->group(...);
Verificarea #28: CORS Configurat Corect
Severitate: MEDIE
// config/cors.php
'allowed_origins' => ['https://yourdomain.com'], // NU ['*'] în producție!
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'allowed_headers' => ['Content-Type', 'Authorization'],
'supports_credentials' => true, // Doar dacă e necesar
Verificarea #29: Token-uri API Rotite
Severitate: MEDIE
// Token-urile Sanctum ar trebui să aibă expirare
'expiration' => 60 * 24, // 24 ore
// Implementează rotația token-urilor
$user->tokens()->delete(); // Invalidează token-urile vechi
$newToken = $user->createToken('api');
Verificarea #30: Fără Date Sensibile în URL-uri
Severitate: RIDICATĂ
# Verifică rutele pentru date sensibile în parametri GET
php artisan route:list | grep -E "GET.*\{.*token\|key\|password"
Vulnerabil:
Route::get('/reset/{token}', ...); // Token în URL = logat peste tot
Mai bine:
Route::post('/reset', ...); // Token în body POST
Categoria 7: Securitate Sistem de Fișiere (4 Verificări)
Verificarea #31: Permisiuni Storage Corecte
Severitate: RIDICATĂ
# Verifică permisiunile
ls -la storage/
# Directoare: 755 (drwxr-xr-x)
# Fișiere: 644 (-rw-r--r--)
# Repară dacă e nevoie
chmod -R 755 storage/
chmod -R 755 bootstrap/cache/
Verificarea #32: Fără PHP în Directoare de Upload
Severitate: CRITICĂ
# Caută fișiere PHP în directoarele de upload
find storage/app/public -name "*.php" -o -name "*.phtml"
find public/uploads -name "*.php" -o -name "*.phtml"
# Ar trebui să nu returneze nimic!
Blochează execuția PHP în uploads:
# public/uploads/.htaccess
<FilesMatch "\.php$">
Order Deny,Allow
Deny from all
</FilesMatch>
Verificarea #33: Link-uri Simbolice Securizate
Severitate: MEDIE
# Verifică link-ul storage
ls -la public/storage
# Ar trebui să indice doar către storage/app/public
Verificarea #34: Fișiere Backup Nu Sunt Accesibile
Severitate: RIDICATĂ
# Verifică fișiere backup în directorul public
find public -name "*.bak" -o -name "*.sql" -o -name "*.zip"
# Ar trebui să nu returneze nimic!
Categoria 8: Dependențe (3 Verificări)
Verificarea #35: Audit Composer Curat
Severitate: CRITICĂ
composer audit
# Ar trebui să returneze: No security vulnerability advisories found
Dacă sunt găsite vulnerabilități:
composer update package/name
# Sau dacă patch-ul nu e disponibil, găsește alternativă
Verificarea #36: Dependențe Actualizate
Severitate: RIDICATĂ
composer outdated
# Revizuiește și actualizează pachetele critice
Update-uri prioritare:
laravel/frameworklivewire/livewirelaravel/sanctum- Orice pachete legate de autentificare
Verificarea #37: Fără Dependențe Dev în Producție
Severitate: MEDIE
# Instalarea în producție ar trebui să folosească --no-dev
composer install --no-dev --optimize-autoloader
Categoria 9: Logging și Monitorizare (2 Verificări)
Verificarea #38: Evenimente de Securitate Loggate
Severitate: RIDICATĂ
// Loghează evenimente importante de securitate
Log::warning('Încercare eșuată de login', [
'email' => $request->email,
'ip' => $request->ip(),
]);
Log::alert('Acțiune admin', [
'user' => auth()->id(),
'action' => 'deleted_user',
'target' => $userId,
]);
Verificarea #39: Fișiere Log Protejate
Severitate: MEDIE
# Logurile nu ar trebui să fie accesibile web
curl https://yoursite.com/storage/logs/laravel.log
# Ar trebui să returneze 403 sau 404, nu conținutul logului
Categoria 10: HTTPS și Headere (1 Verificare)
Verificarea #40: Headere de Securitate Setate
Severitate: RIDICATĂ
// app/Http/Middleware/SecurityHeaders.php
public function handle($request, $next)
{
$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('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
$response->headers->set('Content-Security-Policy', "default-src 'self'");
return $response;
}
Înregistrează în Kernel.php:
protected $middleware = [
\App\Http\Middleware\SecurityHeaders::class,
// ...
];
Script Rapid de Audit
Rulează acest script pentru a verifica problemele critice:
#!/bin/bash
echo "=== Audit Securitate Laravel ==="
echo ""
# Verificare 1: Mod debug
DEBUG=$(grep "APP_DEBUG" .env | cut -d'=' -f2)
if [ "$DEBUG" = "true" ]; then
echo "❌ CRITIC: APP_DEBUG=true"
else
echo "✅ APP_DEBUG=false"
fi
# Verificare 2: Environment
ENV=$(grep "APP_ENV" .env | cut -d'=' -f2)
if [ "$ENV" != "production" ]; then
echo "⚠️ ATENȚIE: APP_ENV=$ENV (nu e production)"
else
echo "✅ APP_ENV=production"
fi
# Verificare 3: APP_KEY setat
KEY=$(grep "APP_KEY" .env | cut -d'=' -f2)
if [[! $KEY == base64:*]]; then
echo "❌ CRITIC: APP_KEY nu e setat corect"
else
echo "✅ APP_KEY configurat"
fi
# Verificare 4: .env în git
if git ls-files | grep -q "^.env$"; then
echo "❌ CRITIC: .env este urmărit în git!"
else
echo "✅ .env nu e în git"
fi
# Verificare 5: Composer audit
echo ""
echo "Rulez composer audit..."
composer audit 2>/dev/null
if [ $? -eq 0 ]; then
echo "✅ Nicio vulnerabilitate cunoscută"
fi
# Verificare 6: Fișiere PHP în uploads
echo ""
PHP_IN_UPLOADS=$(find storage/app/public public/uploads -name "*.php" 2>/dev/null | wc -l)
if [ "$PHP_IN_UPLOADS" -gt 0 ]; then
echo "❌ CRITIC: Fișiere PHP găsite în directoarele de upload!"
find storage/app/public public/uploads -name "*.php" 2>/dev/null
else
echo "✅ Niciun PHP în directoarele de upload"
fi
# Verificare 7: Guarded gol
GUARDED=$(grep -rn "guarded = []" app/Models/ 2>/dev/null | wc -l)
if [ "$GUARDED" -gt 0 ]; then
echo "❌ CRITIC: Modele cu $guarded gol găsite!"
grep -rn "guarded = []" app/Models/
else
echo "✅ Niciun model neguardat"
fi
echo ""
echo "=== Audit Complet ==="
Sumar Checklist
Critice (Trebuie Rezolvate Imediat)
- #1 - Mod debug dezactivat
- #3 - APP_KEY setat corect
- #4 - .env nu e în git
- #19 - Protecție mass assignment
- #20 - SQL injection prevenit
- #21 - Upload-uri fișiere validate
- #32 - Fără PHP în directoare upload
- #35 - Audit Composer curat
Prioritate Ridicată (Rezolvă Săptămâna Aceasta)
- #2 - Environment setat pe production
- #5 - Debugbar dezactivat
- #6 - Telescope securizat
- #7 - register_argc_argv dezactivat
- #9 - Hashing parole puternic
- #10 - Securitate sesiune configurată
- #11 - Throttling login activat
- #14 - Gates returnează boolean explicit
- #16 - Middleware pe rute
- #17 - Rute API protejate
- #18 - Form Requests folosite
- #22 - Prevenire XSS
- #23 - Credențiale bază de date securizate
- #24 - Date sensibile criptate
- #27 - Rate limiting configurat
- #30 - Fără date sensibile în URL-uri
- #31 - Permisiuni storage corecte
- #36 - Dependențe actualizate
- #38 - Evenimente securitate loggate
- #40 - Headere securitate setate
Prioritate Medie (Programează pentru Sprint-ul Următor)
- #8 - Afișare erori dezactivată
- #12 - Expirare reset parolă
- #13 - Implementare MFA
- #15 - Policies înregistrate
- #25 - Backup-uri criptate
- #26 - Fără date sensibile în loguri
- #28 - CORS configurat
- #29 - Token-uri API rotite
- #33 - Link-uri simbolice securizate
- #34 - Fără fișiere backup în public
- #37 - Fără dependențe dev în producție
- #39 - Fișiere log protejate
Următorul: Capitolul 8 - Misiunea Imposibilă: Să Rămâi Securizat Fără Automatizare
Ai văzut 40 de verificări. Imaginează-ți să le rulezi pe 10 aplicații, în fiecare săptămână, în timp ce scrii și feature-uri. Capitolul următor explică de ce securitatea manuală este nesustenabilă - și cum arată alternativa.