Introduzione
La gestione degli errori è un aspetto fondamentale della sicurezza in PHP. Un messaggio visualizzato direttamente all’utente può rivelare informazioni preziose a un attaccante: percorsi del server, nomi delle tabelle, query SQL, variabili d’ambiente, versioni delle librerie o dettagli dell’architettura interna.
Un errore come “Impossibile connettersi al database” è generalmente accettabile per l’utente. Al contrario, messaggi come “Access denied for user app_user using password ... in /var/www/project/config/database.php on line 18” espongono dettagli che non dovrebbero mai essere pubblici.
Una strategia sicura separa due esigenze:
- Mostrare all’utente un messaggio generico e comprensibile.
- Registrare nei log tecnici le informazioni necessarie al debugging e al monitoraggio.
In questo tutorial costruiremo un piccolo sistema di gestione degli errori per un’applicazione PHP. Il codice includerà un identificativo univoco per ogni richiesta, un log strutturato in formato JSON, una pagina di errore generica e una distinzione tra ambiente di sviluppo e produzione.
Codice completo
<?php
declare(strict_types=1);
/*
* In produzione questi valori dovrebbero essere configurati
* tramite variabili d´ambiente, non scritti direttamente nel codice.
*/
const APP_ENV = ´production´;
const LOG_FILE = __DIR__ . ´/../storage/logs/app.log´;
/*
* Genera un identificativo utile per collegare:
* - la risposta mostrata all´utente;
* - la riga corrispondente nel file di log.
*/
function requestId(): string
{
return bin2hex(random_bytes(16));
}
/*
* Scrive eventi strutturati nel log.
* Il formato JSON facilita la ricerca nei sistemi di monitoraggio.
*/
function writeLog(
string $level,
string $message,
string $requestId,
array $context = []
): void {
$directory = dirname(LOG_FILE);
if (!is_dir($directory)) {
mkdir($directory, 0750, true);
}
$entry = [
´timestamp´ => gmdate(´c´),
´level´ => $level,
´request_id´ => $requestId,
´message´ => $message,
´context´ => $context,
];
/*
* Non registrare mai password, token, cookie o dati sensibili.
* Il file dovrebbe essere fuori dalla directory pubblica.
*/
file_put_contents(
LOG_FILE,
json_encode($entry, JSON_UNESCAPED_SLASHES) . PHP_EOL,
FILE_APPEND | LOCK_EX
);
}
/*
* Gestisce gli errori PHP convertendoli in eccezioni.
* In questo modo gli errori seguono un flusso centralizzato.
*/
set_error_handler(
function (
int $severity,
string $message,
string $file,
int $line
): bool {
throw new ErrorException($message, 0, $severity, $file, $line);
}
);
$requestId = requestId();
set_exception_handler(
function (Throwable $exception) use ($requestId): void {
writeLog(
´error´,
$exception->getMessage(),
$requestId,
[
´exception´ => get_class($exception),
´file´ => $exception->getFile(),
´line´ => $exception->getLine(),
´trace´ => APP_ENV === ´development´
? $exception->getTraceAsString()
: ´[omesso in produzione]´,
]
);
http_response_code(500);
header(´Content-Type: text/html; charset=UTF-8´);
if (APP_ENV === ´development´) {
echo ´<h1>Errore di sviluppo</h1>´;
echo ´<pre>´;
echo htmlspecialchars(
$exception->getMessage() . PHP_EOL .
$exception->getFile() . ´:´ . $exception->getLine(),
ENT_QUOTES | ENT_SUBSTITUTE,
´UTF-8´
);
echo ´</pre>´;
return;
}
echo ´<h1>Si è verificato un errore</h1>´;
echo ´<p>Riprova più tardi. Codice assistenza: <strong>´;
echo htmlspecialchars(
$requestId,
ENT_QUOTES | ENT_SUBSTITUTE,
´UTF-8´
);
echo ´</strong></p>´;
}
);
try {
/*
* Esempio volutamente simulato:
* un servizio interno non disponibile.
*/
throw new RuntimeException(
´Servizio pagamenti non raggiungibile´
);
} catch (Throwable $exception) {
/*
* Il gestore globale riceverà l´eccezione rilanciata.
*/
throw $exception;
}
?> Spiegazione
Ambiente di sviluppo e ambiente di produzione
Durante lo sviluppo è utile visualizzare messaggi dettagliati, stack trace e file coinvolti. In produzione, invece, queste informazioni devono rimanere nei log. La costante APP_ENV controlla questo comportamento.
Un errore tecnico non deve diventare una risposta tecnica per l’utente. Il client riceve una risposta HTTP 500 e un messaggio generico, mentre il server salva i dettagli nel log. L’identificativo request_id permette all’assistenza di trovare rapidamente l’evento corretto.
Gestore globale delle eccezioni
set_exception_handler() registra una funzione eseguita quando un’eccezione non viene gestita localmente. Centralizzare la gestione evita che ogni pagina implementi comportamenti diversi e riduce il rischio di dimenticare un controllo importante.
Il tipo Throwable consente di intercettare sia le eccezioni sia gli errori moderni di PHP. Il gestore registra la classe dell’errore, il file e la riga, ma mostra all’esterno solo informazioni sicure.
Conversione degli errori in eccezioni
Molte funzioni PHP possono generare warning invece di eccezioni. Con set_error_handler() possiamo trasformare questi warning in oggetti ErrorException, facendo confluire anche questi casi nel sistema centralizzato.
Questa tecnica deve essere applicata con attenzione: alcune funzioni possono generare errori non intercettabili, come gli errori di sintassi presenti prima dell’esecuzione dello script. È quindi importante mantenere il codice valido e usare strumenti di analisi statica.
Log strutturati e dati sensibili
Il log dell’esempio è in formato JSON. Ogni riga contiene timestamp, livello, messaggio, identificativo della richiesta e contesto. Questo formato è più semplice da analizzare rispetto a messaggi testuali costruiti manualmente.
Non bisogna mai registrare password, token di autenticazione, cookie di sessione, numeri completi di carte di pagamento o dati personali non necessari. Anche i log sono dati sensibili: devono essere protetti con permessi adeguati, conservati per un periodo limitato e consultabili solo da personale autorizzato.
Best practice
- Disabilita display_errors in produzione e abilita la registrazione degli errori sul server.
- Conserva i log fuori dalla directory pubblica del web server.
- Imposta permessi restrittivi, ad esempio directory accessibili solo all’utente del processo PHP.
- Usa la rotazione dei log per evitare file enormi e per applicare una politica di conservazione.
- Non inserire nei log password, token, chiavi API, cookie o dati finanziari.
- Usa un identificativo di richiesta casuale, evitando ID sequenziali facilmente prevedibili.
- Non mostrare stack trace, query SQL, percorsi assoluti o configurazioni interne in produzione.
- Registra eventi di sicurezza importanti, come accessi falliti, modifiche ai ruoli e tentativi di accesso a risorse inesistenti.
- Monitora i log con strumenti centralizzati e configura avvisi per anomalie ripetute.
- Non usare il log per memorizzare dati che l’applicazione non avrebbe motivo di raccogliere.
- Usa HTTPS anche quando mostri il codice assistenza, perché l’identificativo può essere associato a informazioni tecniche interne.
- Testa sia la risposta pubblica sia il contenuto del log per verificare che non ci siano fughe di informazioni.
Riepilogo
Una gestione sicura degli errori impedisce che l’applicazione riveli dettagli interni e, contemporaneamente, mantiene disponibili le informazioni utili agli sviluppatori. La soluzione consiste nel separare chiaramente il comportamento di sviluppo da quello di produzione, centralizzare eccezioni ed errori, usare log strutturati e proteggere i file di log.
Il messaggio mostrato all’utente deve essere breve e generico. Il log deve invece contenere il contesto tecnico indispensabile, senza includere segreti o dati personali. Con un identificativo univoco per richiesta, il team può collegare rapidamente un problema segnalato dall’utente all’evento registrato sul server.
Approfondisci con risorse ufficiali
- PHP Manual — Error Handling: documentazione ufficiale sulle funzioni di gestione degli errori in PHP.
- PHP Manual — Exceptions: riferimento per eccezioni, classi Throwable e gestori globali.
- PHP Manual — ErrorException: dettagli sulla conversione degli errori PHP in eccezioni.
- PHP Manual — error_reporting: configurazione dei livelli di errore da segnalare e registrare.
- OWASP Logging Cheat Sheet: raccomandazioni per progettare log sicuri, utili e rispettosi della privacy.
