Situation actuelle : PHP2Shell. Deux failles PHP critiques (CVSS 9.2) permettent d'exécuter du code malveillant sans authentification. Correctifs disponibles depuis le 21 octobre 2026.Ce qu'il faut faire maintenant →

Type juggling en PHP : les comparaisons lâches comme risque de sécurité

Pourquoi les comparaisons lâches avec == peuvent conduire à des failles de sécurité en PHP, ce qui a changé avec PHP 8.0 et comment écrire des comparaisons robustes pour les connexions, les jetons et les autorisations.

État 7 min de lectureDéveloppement sécurisé

En bref : le type juggling en PHP désigne la conversion automatique de types qui, dans une comparaison lâche avec ==, détermine le résultat. Depuis PHP 8.0, les règles sont plus strictes, mais les chaînes numériques continuent d'être comparées comme des nombres, et true, false et null obéissent à leurs propres règles. Pour les contrôles touchant à la sécurité, la règle est donc : ===, in_array(..., true), match plutôt que switch, hash_equals() pour les jetons et password_verify() pour les mots de passe.

Une comparaison vraie alors qu'elle ne devrait pas l'être

De nombreuses failles de sécurité des applications PHP ne se cachent pas dans des fonctions compliquées, mais dans un seul caractère : un == là où il aurait fallu un ===. Le code semble correct, les tests avec des saisies normales passent, et pourtant, dans certaines conditions, le contrôle laisse passer ce qu'il devrait arrêter.

La cause est une propriété de PHP, pratique au quotidien : les valeurs sont converties au besoin vers un autre type. Une chaîne issue d'un formulaire peut ainsi être comparée directement à un nombre issu de la base de données. Pour les connexions, les jetons, les rôles ou les champs d'état, c'est précisément cette largesse qui peut devenir dangereuse.

Comment PHP compare avec ==

Le manuel PHP décrit en détail les règles des comparaisons lâches. Pour la sécurité, ces cas sont surtout importants :

Comparaison PHP 7 PHP 8 Raison
0 == "a" vrai faux comparées comme chaînes depuis PHP 8.0
"1" == "01" vrai vrai toutes deux numériques, comparaison comme nombre
"10" == "1e1" vrai vrai la notation exponentielle est numérique
100 == "1e2" vrai vrai nombre contre chaîne numérique
null == false vrai vrai comparaison comme valeur booléenne
"abc" == true vrai vrai une chaîne non vide vaut vrai

Le changement de PHP 8.0 a désamorcé toute une classe d'erreurs : auparavant, une chaîne non numérique devenait 0 lors d'une comparaison avec un nombre. Cela touchait aussi les instructions switch. Les autres lignes du tableau montrent toutefois que les comparaisons lâches réservent encore des surprises.

Les opérateurs <, > et l'opérateur de comparaison <=> obéissent eux aussi à ces règles. Pour les tris, c'est généralement sans conséquence ; pour des contrôles comme « montant inférieur à la limite » avec des valeurs de la requête, le type devrait être établi au préalable.

La ligne sur la notation exponentielle est particulièrement perfide. Les valeurs de hachage et les jetons sont souvent composés de chiffres et de lettres. Si une telle valeur ressemble par hasard à un nombre en notation exponentielle, == peut comparer deux hachages différents comme égaux. C'est le contexte de nombreux rapports de contournement de connexion dans d'anciennes applications PHP.

Le type juggling en PHP dans les contrôles de sécurité typiques

Les comparaisons lâches se cachent à plus d'endroits que le seul opérateur == :

  1. in_array() et array_search() comparent de façon lâche sans troisième paramètre. Une liste autorisée de rôles ou d'identifiants devient ainsi plus souple qu'on ne le pense.
  2. switch compare de façon lâche. L'expression match, disponible depuis PHP 8.0, compare au contraire, selon le manuel, de façon stricte avec === et lève une erreur si aucune branche ne correspond.
  3. Les entrées JSON fournissent de vrais types. Un champ que vous attendez comme chaîne peut arriver comme true ou comme nombre et correspondre de façon surprenante avec ==.
  4. strcmp() pour des secrets est inadapté, car le temps d'exécution dépend du contenu et les cas d'erreur sont facilement mal évalués.
  5. Le contrôle des valeurs de retour : des fonctions comme strpos() renvoient 0 pour une occurrence au début et false pour aucune occurrence. if (!strpos(...)) confond les deux.

Comparaisons sûres : le face-à-face

Les exemples suivants montrent chacun la variante dangereuse et la variante sûre :

<?php
declare(strict_types=1);

// Vérifier un rôle
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen);          // dangereux : lâche
in_array($rolle, $erlaubteRollen, true);    // sûr : strict

// Évaluer un statut
$aktion = match ($status) {                 // strict, erreur en cas de valeur inconnue
    'offen'      => 'bearbeiten',
    'erledigt'   => 'archivieren',
};

// Vérifier un jeton issu d'un lien
if ($tokenAusLink == $gespeichertesToken) { /* non sûr */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* sûr */ }

// Vérifier un mot de passe
if (md5($eingabe) == $hash) { /* non sûr : algorithme faible et comparaison souple */ }
if (password_verify($eingabe, $hash)) { /* sûr */ }

// Rechercher une sous-chaîne
if (strpos($text, 'abc') !== false) { /* korrekt: auch Treffer an Position 0 */ }

Selon le manuel, hash_equals() attend le secret connu comme premier paramètre et la valeur de l'utilisateur comme second, et compare en temps constant. La façon de générer des jetons en toute sécurité est décrite dans Nombres aléatoires et jetons sûrs en PHP.

Important

declare(strict_types=1) ne rend pas les comparaisons strictes. L'instruction ne concerne que la conversion de types lors des appels de fonctions et des valeurs de retour. Vous devez remplacer vous-même les comparaisons lâches dans le code.

Les comparaisons strictes ont un revers : elles échouent lorsque les types ne correspondent pas. '42' === 42 est faux. En PHP, les valeurs de formulaires et d'URL arrivent sous forme de chaînes, celles de la base de données sous forme de chaînes ou de nombres selon le pilote et les réglages. Introduire === partout sans harmoniser les types crée des erreurs qui se remarquent en exploitation par le rejet d'utilisateurs légitimes.

La solution est une frontière claire : les saisies sont validées à la réception et converties dans le bon type, par exemple avec filter_var($wert, FILTER_VALIDATE_INT), puis traitées dans le code uniquement avec des déclarations de type et des comparaisons strictes. Les déclarations de type sur les paramètres, les retours et les propriétés y contribuent, car les types erronés se remarquent alors tôt par un message d'erreur, au lieu de disparaître silencieusement dans une comparaison.

Examiner systématiquement le code existant

Dans les projets qui se sont étoffés, on trouve des comparaisons lâches par centaines. Toutes ne posent pas problème. Concentrez-vous sur les endroits où se prend une décision d'accès :

# Comparaisons lâches dans des lignes contenant des termes liés à la sécurité
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array sans comparaison stricte
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"

Des outils d'analyse statique comme PHPStan ou Psalm peuvent, sur demande, signaler en permanence les comparaisons lâches. Ainsi, les nouveaux cas n'arrivent même pas dans la branche principale. Les bases sur les types sont décrites dans Le système de types de PHP ; la façon de convertir d'abord les saisies dans le bon type est montrée dans Valider et filtrer les saisies en PHP.

Ce que cela signifie pour les systèmes anciens

Les applications qui tournent encore sous PHP 7 sont concernées par les anciennes règles de comparaison, plus laxistes. S'y ajoute un second problème : pour PHP 8.1 et antérieures, le projet PHP ne publie plus de mises à jour de sécurité officielles, pas même contre PHP2Shell. La situation est décrite dans PHP 8.1 et antérieures : ce qui s'applique désormais.

Lors d'une migration vers PHP 8, certains résultats de comparaison changent. C'est généralement une amélioration, mais cela peut casser du code qui s'appuyait sur l'ancien comportement. Prévoyez donc des tests qui couvrent spécifiquement les comparaisons avec des nombres, des valeurs vides et null.

Comment PHP SOS aide-t-il concrètement ?

  • Nous parcourons votre code à la recherche de comparaisons lâches aux endroits qui décident de la connexion, des droits ou des paiements.
  • Nous les remplaçons par des comparaisons strictes, des expressions match et des fonctions de contrôle adaptées.
  • Nous configurons l'analyse statique de façon que les nouvelles comparaisons lâches soient repérées dans le build.
  • Nous accompagnons les migrations vers des versions actuelles de PHP avec des tests pour les règles de comparaison modifiées.

Un contrôle de votre logiciel est décrit dans audit de sécurité et test d'intrusion.

Questions fréquentes

Qu'est-ce que le type juggling en PHP ?

PHP convertit automatiquement les valeurs de types différents lors de certaines opérations, par exemple une chaîne de caractères en nombre. Dans une comparaison lâche avec ==, c'est cette conversion qui détermine le résultat. C'est pratique, mais cela peut conduire à des comparaisons vraies alors que les valeurs ne semblent pas égales.

PHP 8 a-t-il résolu le problème ?

En partie. Depuis PHP 8.0, un nombre comparé à une chaîne non numérique est comparé comme chaîne ; 0 == 'a' n'est donc plus vrai. Deux chaînes numériques continuent toutefois d'être comparées comme des nombres, et les comparaisons avec true, false et null obéissent à leurs propres règles.

declare(strict_types=1) aide-t-il contre les comparaisons lâches ?

Non. strict_types détermine si les types scalaires sont convertis automatiquement lors des appels de fonctions et des retours. Il n'a aucune influence sur les opérateurs de comparaison == et !=.

Comment comparer correctement mots de passe et jetons ?

Vous vérifiez les mots de passe avec password_verify, les jetons et les signatures avec hash_equals. Les deux comparent exactement et, pour hash_equals, en temps constant. Une comparaison avec == ou strcmp est inadaptée à cet usage.

Sources

Contact immédiat

Chaque heure compte. Écrivez-nous maintenant.

Décrivez brièvement votre système, votre version de PHP et ce que vous avez observé. Nous vous répondons, faisons le point et travaillons à distance jusqu'à la clôture et la documentation de l'incident.

Aide immédiate