Ce qui se passe
Le 15 octobre 2026, Symfony a publié trois avis de sécurité le même matin. Tous trois font planter un processus PHP, sans authentification ; aucun ne permet d'exécuter du code, et aucune exploitation n'est signalée.
| CVE | Composant | Cause | Versions corrigées |
|---|---|---|---|
| CVE-2026-108082 | HttpFoundation | HeaderUtils::split() analyse Cache-Control avec une expression régulière à retour arrière explosif |
5.4.54, 6.4.46, 7.4.19, 8.1.7 |
| CVE-2026-109413 | Yaml | le lexeur des collections en ligne ([[[…]]]) récurse sans borne |
5.4.54, 6.4.45, 7.4.18, 8.1.6 |
| CVE-2026-109449 | ExpressionLanguage | le parseur construit un arbre sans limite de profondeur | 5.4.54, 6.4.48, 7.4.21, 8.1.9 |
Les mécanismes
HttpFoundation. Le motif acceptait les espaces à l'intérieur de ses
éléments, suivis d'un (?<!\s) : une série d'espaces avant un séparateur pouvait
se répartir de façon exponentielle entre les itérations, et le moteur les
essayait toutes. HeaderBag appelle cette fonction pour Cache-Control à chaque
construction d'une Request : toute application qui reçoit du HTTP est
atteignable. Mesure de l'avis, PHP 8.5, en-tête de 8 Ko : 813 ms pour
17 espaces, et le temps double à chaque espace. À partir de 18, preg_match_all()
abandonne à pcre.backtrack_limit et split() renvoie un tableau vide : toutes
les directives de l'en-tête disparaissent sans bruit. Signalé par r3verii, corrigé
par Nicolas Grekas.
Yaml. lexInlineStructure() appelait lexInlineMapping() et
lexInlineSequence() à chaque { ou [ sans entrer dans le niveau que borne
maxNestingLevel ailleurs dans le parseur. Un document de crochets imbriqués
épuise la pile. Tout code qui lit du YAML non fiable est concerné, y compris
#[MapRequestPayload] quand Yaml est installé et que l'endpoint ne restreint pas
acceptFormat : une requête sans compte, avec un type de contenu YAML, suffit.
ExpressionLanguage. Des dizaines de milliers d'opérateurs unaires, de
littéraux de tableau, d'opérateurs binaires ou d'accès de propriété imbriqués
font planter le processus à la libération de l'arbre. #[Assert\ExpressionSyntax]
passe la valeur à ExpressionLanguage::lint() : une application qui valide des
expressions saisies par ses utilisateurs tombe sur une seule requête. Le parseur
lève désormais une SyntaxError au-delà de 256 niveaux.
Le piège des premiers correctifs
- ExpressionLanguage : la première version (6.4.45, 7.4.18, 8.1.6) ignorait les niveaux ajoutés par une chaîne d'opérateurs binaires ou d'accès de propriété. Soixante niveaux de parenthèses contenant chacun soixante additions passaient encore, et la sérialisation pour le cache par défaut plantait. Le contrôle de profondeur après analyse est dans 6.4.48, 7.4.21 et 8.1.9.
- HeaderUtils : le premier correctif cassait
HeaderUtils::split('foo bar', ' '), qui renvoyait une seule valeur dans 6.4.46 à 6.4.47, 7.4.19 à 7.4.20 et 8.1.7 à 8.1.8. Corrigé dans 6.4.48, 7.4.21 et 8.1.9.
Ne vous arrêtez donc pas à la première version qui porte le correctif : les versions à viser sont 5.4.54, 6.4.48, 7.4.21 et 8.1.9.
Que faire
composer update "symfony/*"
composer show "symfony/*"Vérifier ensuite que symfony/http-foundation, symfony/yaml et
symfony/expression-language sont au moins aux versions ci-dessus, puis lancer
la suite de tests. Si un endpoint n'a pas besoin du YAML, restreindre acceptFormat
pour #[MapRequestPayload].
Source : blog Symfony, CVE-2026-108082, 15 octobre 2026 ; avis liés : CVE-2026-109413 et CVE-2026-109449.