Situazione attuale: PHP2Shell. Due vulnerabilità critiche di PHP (CVSS 9,2) consentono codice dannoso senza autenticazione. Patch dal 21/10/2026.Che cosa fare ora →

Deserializzazione non sicura in PHP: unserialize e sicurezza

unserialize() con dati estranei può, nel caso peggiore, portare all'esecuzione di codice. Come sostituirlo con JSON, limitarlo dove deve restare e trovare i punti a rischio nel codice.

Stato 6 min di letturaGuide

In breve: per la sicurezza di unserialize in PHP vale una regola semplice: non elaborare mai con unserialize() dati che provengono dall'esterno. Usi invece JSON. Dove unserialize() deve restare per via di codice legacy, lo limiti con allowed_classes => false e verifichi l'autenticità dei dati con una firma.

Che cosa può andare storto con unserialize()

PHP può trasformare array e oggetti in una stringa con serialize() e ripristinarli con unserialize(). È comodo per salvare dati in cookie, cache o campi del database. Il problema: unserialize() crea così anche oggetti di classi arbitrarie che possono essere caricate nell'applicazione.

Alla creazione e al più tardi alla distruzione di un simile oggetto, PHP richiama automaticamente determinati metodi, ad esempio __wakeup(), __unserialize() o __destruct(). Se classi del progetto o di librerie contengono tali metodi con azioni su file, database o sistema, un aggressore può usarle in modo improprio tramite dati manipolati. Gli esperti parlano di PHP Object Injection. A seconda delle classi presenti, l'effetto va dalla cancellazione di singoli file all'esecuzione di codice dannoso.

In sintesi per i non tecnici: una sola chiamata sconsiderata nel punto sbagliato può lasciare entrare un aggressore in profondità nell'applicazione. Questa debolezza rientra perciò da anni tra i temi della OWASP Top 10.

L'alternativa sicura: JSON invece di unserialize

Per quasi tutti i casi in cui i dati arrivano dall'esterno o lasciano il server, JSON è la scelta giusta. json_decode() crea solo array, numeri, stringhe e valori booleani, non oggetti di classi proprie.

<?php
declare(strict_types=1);

function leseEinstellungen(string $eingabe): array
{
    try {
        $daten = json_decode($eingabe, true, 16, JSON_THROW_ON_ERROR);
    } catch (JsonException $e) {
        throw new InvalidArgumentException('Ungültige Daten.');
    }

    if (!is_array($daten)) {
        throw new InvalidArgumentException('Erwartet wurde ein Objekt.');
    }

    // Accettare solo i campi noti con i tipi attesi
    return [
        'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
        'seiten'  => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
    ];
}

È importante la seconda parte: anche i dati JSON sono input. Accetti solo i campi attesi e verifichi tipi e intervalli di valori prima di lavorarci.

Quando unserialize è inevitabile: allowed_classes e firma

Nelle applicazioni più vecchie i dati serializzati si trovano spesso in database o cache che non si possono convertire subito. Allora limiti il rischio in due passi. Primo, impedisca con allowed_classes la creazione di oggetti. Secondo, garantisca con una firma HMAC che i dati provengano davvero dalla Sua applicazione e non siano stati modificati lungo il percorso.

<?php
declare(strict_types=1);

const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // mai nel repository del codice

function verpacken(array $daten): string
{
    $roh = serialize($daten);
    return hash_hmac('sha256', $roh, SCHLUESSEL) . '.' . base64_encode($roh);
}

function auspacken(string $paket): array
{
    [$signatur, $b64] = array_pad(explode('.', $paket, 2), 2, '');
    $roh = base64_decode($b64, true);

    if ($roh === false || !hash_equals(hash_hmac('sha256', $roh, SCHLUESSEL), $signatur)) {
        throw new RuntimeException('Signatur ungültig.');
    }

    $daten = unserialize($roh, ['allowed_classes' => false, 'max_depth' => 16]);
    if (!is_array($daten)) {
        throw new RuntimeException('Unerwartetes Format.');
    }
    return $daten;
}

Se servono comunque determinate classi, le indichi una per una in allowed_classes, invece di consentirle tutte. La chiave va nella configurazione del server o in un archivio di segreti e deve essere rinnovata dopo un incidente.

Convertire gradualmente i dati esistenti a JSON

Nelle applicazioni cresciute negli anni i valori serializzati restano spesso per anni nel database. Una conversione in un solo passo è allora rischiosa. Si è dimostrato valido un passaggio graduale: l'applicazione legge entrambi i formati, ma scrive solo più JSON. Alla lettura di un vecchio valore, questo viene decompresso una sola volta con allowed_classes => false e subito riscritto come JSON. Dopo un po' la maggior parte delle voci è convertita e uno script una tantum si occupa del resto.

Durante il passaggio registri quanto spesso viene ancora letto il vecchio formato e anche quando le firme non corrispondono. Tali fallimenti possono indicare tentativi di manipolazione e rientrano nel monitoraggio. Solo quando non compare più alcun vecchio formato, rimuova del tutto dal codice il ramo con unserialize().

Deserializzazione nascosta: sessioni, cache e percorsi di file

Non ogni deserializzazione compare in modo visibile nel codice proprio. Controlli anche questi punti:

  • Sessioni: PHP serializza automaticamente i dati di sessione. Finché gli aggressori non possono scrivere i file di sessione, non è critico. Percorsi di sessione separati per ogni sito sono descritti nella guida Mettere in sicurezza le sessioni in PHP.
  • Serializzatore delle sessioni: l'avviso su CVE-2026-115560 riguarda una configurazione insolita in cui il serialize_handler viene cambiato durante l'esecuzione con ini_set() e l'upload progress delle sessioni è attivo. Misura secondo php.net: session.upload_progress.enabled=0 nel php.ini o nel pool FPM, poi riavvio dei worker.
  • Cache e code: Redis, Memcached o tabelle di database con valori serializzati sono sicuri quanto l'accesso a essi. Non vanno esposti apertamente in rete.
  • Funzioni sui file con percorsi da input: non passi mai alle funzioni sui file percorsi presi dalla richiesta. Gli stream wrapper come phar:// possono altrimenti innescare elaborazioni inattese. Un elenco di consentiti per i nomi dei file protegge qui in modo più affidabile di qualsiasi filtraggio.
  • Dipendenze: le librerie con debolezze note ampliano le possibilità di un aggressore. Le tenga aggiornate, come descritto nella guida Verificare le vulnerabilità delle dipendenze Composer.

Trovare i punti a rischio nel codice

Un primo inventario riesce con una semplice ricerca. Non modifica nulla nel sistema e mostra tutte le chiamate al di fuori della directory vendor:

grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
  | grep -v '/vendor/'

Valuti ogni riscontro con tre domande: da dove vengono i dati? Un utente può modificarli? allowed_classes è impostato? I riscontri con input da cookie, $_GET, $_POST, $_REQUEST o interfacce esterne hanno la precedenza. Li sostituisca con JSON e provi la conversione prima di rilasciarla.

Un ulteriore indizio si trova nel browser. I dati PHP serializzati hanno una struttura tipica con identificatori di tipo e indicazioni di lunghezza. Se trova tali valori in cookie, campi nascosti dei moduli o parametri del Suo sito, l'applicazione ha consegnato dati serializzati al visitatore e presumibilmente se li aspetta di ritorno. Proprio questi punti vanno controllati.

Per la direzione basta una semplice domanda a sviluppatori o agenzia: «La nostra applicazione elabora da qualche parte dati serializzati che provengono dall'esterno e, in caso affermativo, come sono protetti?» Una risposta chiara con i punti in cui si trovano è un buon segno. Una risposta evasiva è un motivo per far verificare l'applicazione.

Come aiuta concretamente PHP SOS?

  • Cerchiamo nel Suo codice la deserializzazione con dati estranei e valutiamo ogni ritrovamento per raggiungibilità e impatto.
  • Proponiamo passaggi concreti a JSON o chiamate protette e affianchiamo i Suoi sviluppatori nell'attuazione.
  • Verifichiamo le dipendenze alla ricerca di debolezze note e documentiamo lo stato.
  • Su richiesta facciamo verificare l'applicazione in modo indipendente nell'ambito di un audit di sicurezza e penetration test.

Domande frequenti

unserialize() in PHP è insicuro per principio?

Non per dati che genera solo la Sua applicazione e che nessuno può modificare. Diventa pericoloso non appena utenti o terzi possono influenzare l'input, ad esempio tramite cookie, campi di moduli o interfacce esterne.

allowed_classes => false basta come protezione?

Impedisce la creazione di oggetti arbitrari e chiude così la via d'attacco più importante. Per input dall'esterno JSON resta comunque la scelta migliore, perché il parser è più semplice e non ricostruisce oggetti.

Un aggiornamento delle librerie aiuta contro gli attacchi di deserializzazione?

In parte. Gli attacchi sfruttano spesso classi delle dipendenze che eseguono azioni quando un oggetto viene distrutto o risvegliato. I pacchetti aggiornati chiudono le catene note, ma la vera causa è la chiamata a unserialize con dati estranei.

PHP2Shell ha qualcosa a che fare con la deserializzazione?

Le due vulnerabilità principali si trovano nell'elaborazione delle richieste di PHP. L'avviso su CVE-2026-115560 riguarda invece una configurazione insolita della serializzazione delle sessioni. La misura lì è session.upload_progress.enabled=0.

Fonti

Contatto immediato

Ogni ora conta. Ci scriva adesso.

Descriva brevemente sistema, versione di PHP e quanto osservato. Ci faremo sentire, chiariremo la situazione e lavoreremo da remoto fino alla chiusura e alla documentazione dell'incidente.

Aiuto immediato