Penetration testing di un'applicazione Laravel: metodologia e vulnerabilità tipiche

Penetration testing di un'applicazione Laravel: metodologia e vulnerabilità tipiche

A luglio 2025 ho condotto un penetration test su un gestionale Laravel per un'azienda del settore manifatturiero con 45 dipendenti. Il gestionale - Laravel 10, PHP 8.2, MySQL 8.0 su VPS Hetzner - gestiva ordini, fatturazione, gestione magazzino e anagrafica clienti con dati di circa 3.000 aziende clienti, inclusi partite IVA, indirizzi di spedizione e storico ordini. L'applicazione era in produzione da quattro anni, sviluppata da un team di due programmatori junior che non avevano mai ricevuto formazione specifica sulla sicurezza applicativa. Il titolare mi aveva contattato perché un suo cliente enterprise aveva richiesto, come prerequisito contrattuale, un report di penetration test dell'applicazione. La domanda implicita era: "Quanto siamo vulnerabili?"

La risposta, dopo cinque giorni di test, era: molto più di quanto chiunque si aspettasse. Ho trovato 14 vulnerabilità, di cui 3 critiche (mass assignment che permetteva l'escalation di privilegi, IDOR su quattro endpoint API che esponeva i dati di tutti i 3.000 clienti, e un file .env accessibile via web con le credenziali del database), 5 alte (XSS stored nel campo note degli ordini, CSRF mancante su operazioni di modifica, assenza di rate limiting sul login, sessioni che non venivano invalidate al logout, e upload di file senza validazione del tipo MIME), e 6 medie (header di sicurezza mancanti, cookie senza flag Secure e HttpOnly, informazioni di debug nelle risposte di errore, versione di PHP esposta negli header, directory listing attivo su alcune path, e password policy debole senza requisiti di complessità). Nessuna di queste vulnerabilità richiedeva strumenti sofisticati per essere trovata - bastava sapere dove guardare e cosa testare.

Come si conduce un penetration test su un'applicazione Laravel?

La metodologia che uso segue la struttura dell'OWASP Testing Guide, adattata alle specificità di Laravel e del suo ecosistema. Il test si articola in cinque fasi: ricognizione, mapping dell'applicazione, identificazione delle vulnerabilità, exploitation e verifica, e reportistica. La durata tipica per un'applicazione di media complessità (30-80 endpoint, autenticazione con ruoli, upload file, API REST) è di 3-5 giorni lavorativi. Non è un tempo arbitrario - è il tempo che serve per testare sistematicamente ogni categoria OWASP senza scorciatoie e senza lasciare angoli ciechi.

Fase 1 - Ricognizione. Prima di toccare l'applicazione, raccolgo informazioni dall'esterno: DNS, certificati TLS, header HTTP, tecnologie rilevate (Wappalyzer), Google dorking per file esposti, e scan delle porte con nmap se il perimetro include il server. Su un'applicazione Laravel, i segnali rivelatori sono: l'header X-Powered-By: PHP/8.2.x (espone la versione di PHP), il cookie laravel_session (conferma il framework), e la risposta a /api/ o /telescope (possibili endpoint di debug esposti). La ricognizione non è un "nice to have" - è la fase che mi dice dove concentrare il testing nelle fasi successive.

Fase 2 - Mapping. Esploro sistematicamente l'applicazione come utente autenticato con diversi livelli di privilegio (admin, utente standard, utente read-only) per mappare tutti gli endpoint, i form, le funzionalità di upload, i flussi di autenticazione e le API. Uso Burp Suite come proxy per intercettare e registrare tutte le richieste HTTP - alla fine di questa fase ho una mappa completa delle superfici di attacco dell'applicazione, con ogni endpoint documentato con metodo HTTP, parametri accettati, tipo di risposta e livello di autenticazione richiesto.

Fase 3 - Testing. Per ogni endpoint mappato, testo le categorie OWASP rilevanti. Su Laravel, i test prioritari sono: broken access control (IDOR, mancanza di autorizzazione, escalation di privilegi via mass assignment), injection (SQL injection nelle query raw, XSS stored e reflected, command injection nei parametri passati a funzioni di sistema), security misconfiguration (debug mode, file .env, directory listing, header di sicurezza), e authentication/session management (brute force login, session fixation, cookie flags).

Nel mio profilo professionale trovi il dettaglio dell'esperienza offensive che porto in questi assessment - la differenza tra un vulnerability scan automatizzato (che chiunque può eseguire con OWASP ZAP) e un penetration test manuale è la capacità di ragionare come un attaccante, concatenare vulnerabilità minori in catene di attacco significative, e testare la logica di business oltre la superficie tecnica.

Mass assignment: la vulnerabilità Laravel che trovo nel 40% dei progetti

Il mass assignment è una vulnerabilità specifica di Laravel (e di altri framework con ORM che supportano l'assegnazione massiva delle proprietà) che permette a un attaccante di modificare campi del database che non erano previsti dal form. Il meccanismo è questo: se un controller usa $request->all() o $request->validated() senza un $fillable esplicito nel model, un attaccante può aggiungere campi extra nella richiesta che vengono scritti direttamente nel database. L'esempio classico: un form di registrazione utente che accetta name, email e password. Se l'attaccante aggiunge is_admin=1 nel body della richiesta e il model User non ha $fillable configurato, l'utente viene creato come admin.

Il test che eseguo è semplice: intercetto la richiesta POST di un form con Burp Suite, aggiungo campi extra che corrispondono a colonne note del database (is_admin, role, status, verified, balance), e verifico se i valori vengono salvati. Nel gestionale del cliente manifatturiero, il mass assignment permetteva di cambiare il campo ruolo dell'utente da operatore a admin tramite la richiesta di aggiornamento profilo - un'escalation di privilegi che dava accesso completo al gestionale, inclusi i dati finanziari e le anagrafiche di tutti i clienti.

La fix è una riga nel model: protected $fillable = ['name', 'email', 'password']; - dichiarando esplicitamente quali campi possono essere assegnati via mass assignment. In alternativa, e preferibilmente, usare $request->only(['name', 'email', 'password']) nel controller per filtrare esplicitamente i campi accettati. La regola operativa è: non usare mai $request->all() in un controller che scrive nel database. Mai. In nessun caso.

IDOR: quattro endpoint, 3.000 clienti esposti

L'Insecure Direct Object Reference (IDOR) è la variante più frequente del broken access control. Il pattern: un endpoint GET /api/clienti/{id} che restituisce i dati del cliente con quell'ID a qualsiasi utente autenticato, senza verificare che l'utente abbia il permesso di accedere a quel cliente specifico. L'attaccante deve solo iterare gli ID da 1 a 3.000 per scaricare l'intera anagrafica clienti.

Nel gestionale, ho trovato IDOR su quattro endpoint: visualizzazione cliente (GET /api/clienti/{id}), download fattura (GET /api/fatture/{id}/pdf), dettaglio ordine (GET /api/ordini/{id}), e storico movimenti magazzino (GET /api/movimenti?cliente_id={id}). In tutti e quattro i casi, il middleware auth verificava che l'utente fosse loggato, ma nessuna Policy o Gate verificava che l'utente avesse accesso alla risorsa specifica. Con un semplice script bash che iterava gli ID e salvava le risposte JSON, ho estratto l'intera anagrafica di 3.000 clienti con partite IVA, indirizzi e storico ordini in meno di 5 minuti.

La fix per ciascun endpoint è una Policy Laravel:

// app/Policies/ClientePolicy.php
public function view(User $user, Cliente $cliente): bool
{
    // L'utente può vedere solo i clienti della sua azienda
    return $user->azienda_id === $cliente->azienda_id;
}

// Nel controller: autorizzazione esplicita
public function show(Cliente $cliente): ClienteResource
{
    $this->authorize('view', $cliente);
    return new ClienteResource($cliente);
}

Il file .env esposto: la vulnerabilità più grave trovata in 10 secondi

La vulnerabilità con la severity più alta non è stata trovata con Burp Suite o con uno scanner automatizzato - è stata trovata digitando https://gestionale.cliente.it/.env nel browser. Il file .env di Laravel, che contiene le credenziali del database, la chiave dell'applicazione (APP_KEY), le chiavi API di servizi esterni (Stripe, SendGrid, Cloudflare), e il seed del token CSRF, era accessibile via web senza autenticazione. La causa: il server Nginx era configurato con root /var/www/gestionale/ invece di root /var/www/gestionale/public/ - puntando alla directory radice del progetto Laravel invece che alla directory public/ dove il web server dovrebbe servire i file. Con questa misconfiguration, non solo il .env era esposto, ma anche l'intera struttura del progetto: composer.json (che rivela tutte le dipendenze e le loro versioni), storage/logs/laravel.log (che contiene stack trace con informazioni interne), e potenzialmente il database SQLite se usato per testing.

Le conseguenze della chiave APP_KEY esposta meritano un approfondimento. L'APP_KEY di Laravel è la chiave crittografica usata per cifrare i cookie di sessione, i token CSRF e qualsiasi dato cifrato con Crypt::encrypt(). Un attaccante con l'APP_KEY può decifrare i cookie di sessione, impersonare qualsiasi utente senza conoscerne la password, e forgiare token CSRF validi per eseguire operazioni a nome della vittima. In pratica, l'APP_KEY esposta equivale a un accesso admin all'applicazione - senza nemmeno dover sfruttare un bug nel codice. La fix immediata è duplice: correggere la configurazione Nginx (puntare a /public/) e ruotare l'APP_KEY con php artisan key:generate, che invalida tutte le sessioni esistenti e tutti i dati cifrati con la chiave precedente. La fix preventiva è aggiungere una regola Nginx che blocca esplicitamente l'accesso ai file dot, indipendentemente dalla directory root configurata - una difesa in profondità che protegge anche da misconfiguration future.

Quanto costa un penetration test per una PMI?

La domanda che ogni titolare di PMI fa prima di autorizzare un penetration test è: "Quanto costa?" La risposta onesta è: dipende dalla complessità dell'applicazione, dal perimetro del test e dalla profondità richiesta. Per un'applicazione Laravel di media complessità (30-80 endpoint, autenticazione multi-ruolo, API REST, upload file), un penetration test completo secondo la metodologia OWASP richiede 3-5 giorni lavorativi di un consulente senior - un investimento nell'ordine di alcune migliaia di euro. Per una PMI che fattura 1-5 milioni l'anno, è una frazione del costo di un incidente di sicurezza (che il Rapporto Clusit 2025 quantifica in media in 180.000 euro per le PMI italiane, considerando downtime, remediation, perdita di clienti e sanzioni GDPR). Il rapporto costo/beneficio è nettamente a favore del test preventivo - ma molte PMI lo scoprono solo dopo il primo incidente, quando il costo della remediation post-incidente è 10-50 volte superiore a quello della prevenzione.

Un aspetto che molti titolari non considerano è che il penetration test ha un valore contrattuale crescente: sempre più clienti enterprise e gare pubbliche richiedono un report di pen test come prerequisito per la partnership. Il gestionale del cliente manifatturiero è stato testato proprio per questo motivo - e il report di remediation completato (vulnerabilità trovate → corrette → re-testate) è diventato un asset commerciale che ha sbloccato un contratto con un cliente enterprise da 400.000 euro l'anno. L'investimento nel pen test si è ripagato letteralmente nella prima settimana dopo il completamento delle fix.

Il report: cosa consegno al cliente e come prioritizzo le fix

Il deliverable finale del penetration test è un report strutturato che contiene: un executive summary per il management (3 vulnerabilità critiche trovate, rischio di esposizione di 3.000 anagrafiche clienti, 5 giorni stimati per la remediation delle vulnerabilità critiche), una sezione tecnica per ogni vulnerabilità trovata (descrizione, severity CVSS, proof of concept con screenshot, remediation suggerita con codice), e un piano di prioritizzazione delle fix basato su severity × exploitability (le vulnerabilità critiche facili da sfruttare prima, le vulnerabilità medie difficili da sfruttare per ultime).

La prioritizzazione che uso è: giorno 1 - fixare il file .env esposto (modifica Nginx, 10 minuti) e il mass assignment (aggiungere $fillable ai model, 30 minuti); giorno 2 - fixare gli IDOR con le Policy Laravel (4 endpoint, 2 ore); giorno 3 - implementare rate limiting sul login, aggiungere header di sicurezza, fixare il XSS stored; giorni 4-5 - session management, CSRF, cookie flags, password policy. Le fix dei giorni 1-2 eliminano il 70% del rischio totale con il 20% dello sforzo - il principio di Pareto applicato alla sicurezza.

Dopo la remediation, eseguo un re-test sulle vulnerabilità fixate per verificare che le fix siano corrette e complete. Ho documentato il framework di audit di sicurezza per applicazioni PHP legacy in un articolo separato che copre la metodologia da una prospettiva più difensiva - questo articolo è il complemento offensivo. Se la tua applicazione Laravel non è mai stata sottoposta a un penetration test e vuoi sapere dove sei vulnerabile prima che lo scopra qualcun altro, contattami per un assessment: in 3-5 giorni testo sistematicamente ogni superficie di attacco, produco un report con proof of concept e remediation, e ti accompagno nella correzione delle vulnerabilità critiche.

Ultima modifica: