🔒 Hacked
Capitolul 7

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.

40
Verificări de Securitate
10
Categorii
~2h
Timp Audit Complet
Breșe Prevenite

Cum să Folosești Acest Checklist

Fiecare verificare include:

Abordare recomandată:

  1. Rulează mai întâi toate verificările CRITICE
  2. Adresează problemele cu severitate RIDICATĂ
  3. Programează MEDIE/SCĂZUTĂ pentru sprint-ul următor
  4. 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:


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>

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:


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:

security-audit.sh
#!/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)

Prioritate Ridicată (Rezolvă Săptămâna Aceasta)

Prioritate Medie (Programează pentru Sprint-ul Următor)


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.