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 →

Type juggling in PHP: i confronti laschi come rischio di sicurezza

Perché i confronti laschi con == in PHP possono causare vulnerabilità, che cosa è cambiato con PHP 8.0 e come scrivere in modo robusto i confronti in login, token e autorizzazioni.

Stato 6 min di letturaSviluppo sicuro

In breve: il type juggling in PHP indica la conversione automatica dei tipi che, nel confronto lasco con ==, decide il risultato. Da PHP 8.0 le regole sono più severe, ma le stringhe numeriche continuano a essere confrontate come numeri, e true, false e null seguono regole proprie. Per le verifiche rilevanti per la sicurezza vale quindi: ===, in_array(..., true), match al posto di switch, hash_equals() per i token e password_verify() per le password.

Un confronto vero anche se non dovrebbe esserlo

Molte vulnerabilità nelle applicazioni PHP non si nascondono in funzioni complicate, ma in un solo carattere: un == dove avrebbe dovuto esserci un ===. Il codice sembra corretto, i test con input normali passano, e tuttavia in certe condizioni la verifica lascia passare qualcosa che avrebbe dovuto fermare.

La causa è una caratteristica di PHP, pratica nell'uso quotidiano: i valori vengono convertiti in un altro tipo quando serve. Una stringa proveniente da un modulo può così essere confrontata senza passaggi intermedi con un numero del database. In login, token, ruoli o campi di stato, proprio questa generosità può diventare pericolosa.

Come PHP confronta con ==

Il manuale PHP descrive in dettaglio le regole per i confronti laschi. Per la sicurezza contano soprattutto questi casi:

Confronto PHP 7 PHP 8 Motivo
0 == "a" vero falso Da PHP 8.0 confrontati come stringhe
"1" == "01" vero vero Entrambi numerici, confronto come numero
"10" == "1e1" vero vero La notazione esponenziale è numerica
100 == "1e2" vero vero Numero contro stringa numerica
null == false vero vero Confronto come valore di verità
"abc" == true vero vero Una stringa non vuota vale come vera

La modifica in PHP 8.0 ha disinnescato un'intera classe di errori: in precedenza una stringa non numerica nel confronto con un numero diventava 0. Ciò riguardava anche le istruzioni switch. Le altre righe della tabella mostrano però che i confronti laschi continuano a riservare sorprese.

Anche gli operatori <, > e l'operatore di confronto <=> seguono queste regole. Negli ordinamenti di solito è innocuo, ma in verifiche come «importo inferiore al limite» con valori provenienti dalla richiesta il tipo dovrebbe essere già stabilito.

Particolarmente insidiosa è la riga con la notazione esponenziale. Valori di hash e token sono spesso composti da cifre e lettere. Se un valore del genere appare per caso come un numero in notazione esponenziale, == può confrontare come uguali due hash diversi. È lo sfondo di molte segnalazioni di aggiramento del login in applicazioni PHP più vecchie.

Type juggling in PHP nelle tipiche verifiche di sicurezza

I confronti laschi si nascondono in più punti del solo operatore ==:

  1. in_array() e array_search() confrontano in modo lasco senza il terzo parametro. Una lista consentita con ruoli o ID diventa così più morbida del previsto.
  2. switch confronta in modo lasco. L'espressione match, disponibile da PHP 8.0, confronta invece secondo il manuale con sicurezza di tipo, con ===, e genera un errore se nessun ramo corrisponde.
  3. Gli input JSON forniscono tipi reali. Un campo che si aspetta come stringa può arrivare come true o come numero e coincidere in modo sorprendente con ==.
  4. strcmp() per i segreti non è adatta, perché la durata dipende dal contenuto e i casi di errore vengono facilmente valutati in modo sbagliato.
  5. Verifica dei valori di ritorno: funzioni come strpos() restituiscono 0 per un riscontro all'inizio e false per nessun riscontro. if (!strpos(...)) confonde le due cose.

Confronti sicuri: la contrapposizione

Gli esempi seguenti mostrano di volta in volta la variante insicura e quella sicura:

<?php
declare(strict_types=1);

// Verificare il ruolo
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen);          // insicuro: lasco
in_array($rolle, $erlaubteRollen, true);    // sicuro: rigoroso

// Valutare lo stato
$aktion = match ($status) {                 // rigoroso, errore con valore sconosciuto
    'offen'      => 'bearbeiten',
    'erledigt'   => 'archivieren',
};

// Verificare il token dal link
if ($tokenAusLink == $gespeichertesToken) { /* non sicuro */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* sicuro */ }

// Verificare la password
if (md5($eingabe) == $hash) { /* non sicuro: algoritmo debole e confronto non rigoroso */ }
if (password_verify($eingabe, $hash)) { /* sicuro */ }

// Cercare una sottostringa
if (strpos($text, 'abc') !== false) { /* korrekt: auch Treffer an Position 0 */ }

Secondo il manuale hash_equals() si aspetta come primo parametro il segreto noto e come secondo il valore dell'utente, e confronta in tempo costante. Come generare i token in sicurezza lo descrive Numeri casuali e token sicuri in PHP.

Importante

declare(strict_types=1) non rende rigorosi i confronti. L'istruzione riguarda solo la conversione dei tipi nelle chiamate di funzione e nei valori di ritorno. I confronti laschi deve sostituirli lei stessa nel codice.

I confronti rigorosi hanno un rovescio: falliscono quando i tipi non coincidono. '42' === 42 è falso. I valori di moduli e URL arrivano in PHP come stringhe, quelli del database a seconda di driver e impostazione come stringhe o numeri. Chi introduce ovunque === senza allineare i tipi crea errori che in esercizio si notano come rifiuto di utenti legittimi.

La soluzione è un confine chiaro: gli input vengono validati alla ricezione e portati al tipo giusto, ad esempio con filter_var($wert, FILTER_VALIDATE_INT), e poi nel codice trattati solo con dichiarazioni di tipo e confronti rigorosi. Le dichiarazioni di tipo su parametri, valori di ritorno e proprietà aiutano, perché i tipi sbagliati emergono presto con un messaggio di errore, anziché sparire in silenzio in un confronto.

Verificare sistematicamente il codice esistente

Nei progetti cresciuti nel tempo i confronti laschi si contano a centinaia. Non tutti sono un problema. Si concentri sui punti in cui si decide l'accesso:

# Confronti laschi nelle righe con termini rilevanti per la sicurezza
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array senza confronto rigoroso
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"

Strumenti di analisi statica come PHPStan o Psalm possono, su richiesta, segnalare stabilmente i confronti laschi. Così i nuovi casi non arrivano nemmeno nel ramo principale. Le basi sui tipi le descrive Il sistema dei tipi di PHP; come portare prima gli input al tipo giusto lo mostra Validare e filtrare gli input in PHP.

Che cosa significa per i sistemi legacy

Le applicazioni che girano ancora su PHP 7 sono interessate dalle vecchie regole di confronto, più lasche. A ciò si aggiunge un secondo problema: per PHP 8.1 e precedenti il progetto PHP non fornisce più aggiornamenti di sicurezza ufficiali, nemmeno contro PHP2Shell. La situazione la descrive PHP 8.1 e precedenti: che cosa vale ora.

Con una migrazione a PHP 8 alcuni risultati dei confronti cambiano. Di solito è un miglioramento, ma può rompere codice che si affidava al vecchio comportamento. Preveda quindi test che coprano in modo mirato i confronti con numeri, valori vuoti e null.

Come aiuta concretamente PHP SOS?

  • Cerchiamo nel suo codice i confronti laschi nei punti che decidono su login, permessi o pagamenti.
  • Li sostituiamo con confronti rigorosi, espressioni match e funzioni di verifica adatte.
  • Configuriamo l'analisi statica in modo che i nuovi confronti laschi emergano nella build.
  • Accompagniamo le migrazioni alle versioni attuali di PHP con test per le regole di confronto modificate.

Una verifica del suo software è descritta in audit di sicurezza e penetration test.

Domande frequenti

Che cos'è il type juggling in PHP?

In alcune operazioni PHP converte automaticamente valori di tipo diverso, ad esempio una stringa in un numero. Nel confronto lasco con == questa conversione decide il risultato. È comodo, ma può portare a confronti veri anche se i valori non sembrano uguali.

PHP 8 ha risolto il problema?

In parte. Da PHP 8.0 un numero confrontato con una stringa non numerica viene confrontato come stringa, quindi 0 == 'a' non è più vero. Due stringhe numeriche continuano però a essere confrontate come numeri, e i confronti con true, false e null seguono regole proprie.

declare(strict_types=1) aiuta contro i confronti laschi?

No. strict_types stabilisce se i tipi scalari vengano convertiti automaticamente nelle chiamate di funzione e nei valori di ritorno. Sugli operatori di confronto == e != non ha alcun effetto.

Come confronto correttamente password e token?

Le password si verificano con password_verify, token e firme con hash_equals. Entrambe confrontano in modo esatto e, nel caso di hash_equals, in tempo costante. Un confronto con == o strcmp non è adatto a questo scopo.

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