Un bug critique de PhpSpreadsheet a contourné son propre correctif

Il y a quelques semaines, l'équipe PHPOffice a publié un correctif pour un bug de désérialisation phar dans PhpSpreadsheet, référencé sous CVE-2026-34084. Des chercheurs ont ensuite trouvé un moyen de contourner ce correctif, et le bypass est désormais sa propre vulnérabilité : CVE-2026-45034, évalué à 9,2 critique selon CVSS v4. L'avis, GHSA-87m4-826x-3crx, est devenu public le 7 juin. Si PhpSpreadsheet lit des fichiers tableur quelque part dans votre pile, cela vaut dix minutes aujourd'hui.
Le correctif original a ajouté une fonction utilitaire appelée File::prohibitWrappers, censée rejeter les wrappers de flux comme phar://. Il le faisait en appelant parse_url($filename, PHP_URL_SCHEME) et en levant une exception si le schéma renvoyait une chaîne de plus d'un caractère. Le problème est que cette vérification ne répond pas à la même question que « ce chemin utilise-t-il un wrapper ? ». Passez-lui un chemin de la forme phar:///path/file.phar/inner, avec trois slashes après le schéma, et parse_url renvoie le booléen false au lieu de la chaîne du schéma. La vérification est ignorée, la fonction retourne sans se plaindre, et IOFactory::load() atteint quand même le wrapper phar.
Pourquoi la version PHP importe plus que le score CVSS
Ce qui se passe ensuite dépend entièrement de votre version PHP, et c'est là que les équipes se trompent souvent. Sur PHP 7.x, il suffit de toucher le wrapper phar via is_file pour que PHP désérialise les métadonnées du phar, ce qui déclenche __wakeup et __destruct sur des objets contrôlés par l'attaquant. C'est une exécution de code à distance complète. Les chercheurs l'ont prouvé sur la branche 1.x sous PHP 7.4 avec une chaîne de gadgets qui écrivait un fichier marqueur. Sur PHP 8.x, la désérialisation automatique des métadonnées pour les opérations de fichiers simples a été supprimée, donc au niveau de PhpSpreadsheet le bug se réduit à une primitive de lecture de fichier phar. L'exécution de code ne revient que si quelque chose en aval dans votre application appelle Phar::getMetadata sur ce chemin.
Interprétez-le ainsi. Vous êtes encore sur PHP 7.x ? C'est critique et immédiat. Sur PHP 8.x, vous n'êtes pas automatiquement à l'abri : un seul appel à Phar::getMetadata suffit pour obtenir le même résultat, et une lecture arbitraire de fichier phar n'est pas un bug que vous voulez voir traîner dans un endpoint d'import de documents.
Ce que cela nous dit sur la manière dont les correctifs sont contournés
Lors de nos missions de pentesting, la désérialisation phar est l'une des premières choses que nous vérifions quand une application prend un chemin de fichier, un nom de fichier ou un upload et le transmet à une bibliothèque. Ce qui est intéressant dans ce CVE, ce n'est pas l'astuce du wrapper, c'est la forme du correctif original. Le patch validait le schéma que parse_url renvoyait, alors que la vraie règle était « ce chemin ne doit en aucun cas référencer un wrapper ». Corrigez le symptôme visible, et vous laissez grands ouverts les cas que vous n'avez pas testés. Trois slashes, c'est tout ce qu'il a fallu.
Nous observons le même réflexe en dehors de PHP. Dans nos travaux PHP et Docker pour créer des applications cloud-native pour des clients entreprises, les bibliothèques qui analysent des entrées non fiables sont celles qui méritent une attention particulière à chaque mise à jour de dépendance, pas seulement quand un CVE apparaît. Dans nos projets iOS et Android natifs, le schéma se répète avec des SDK tiers qui acceptent silencieusement une URL ou un chemin. L'équipe fait confiance à la bibliothèque, la bibliothèque fait confiance à l'entrée, et personne ne contrôle la frontière entre les deux. Un nouveau correctif n'est pas la fin d'une classe de vulnérabilité, c'est un point de données supplémentaire sur l'emplacement réel de cette frontière.
Que faire cette semaine
Trois étapes concrètes, dans l'ordre :
- Mettez à jour PhpSpreadsheet. Le correctif est disponible en 1.30.5 sur la branche 1.x, ou dans la version patchée de la branche que vous suivez : 2.1.17, 2.4.6, 3.10.6 ou 5.8.0. Le nouveau code abandonne l'astuce
parse_urlet identifiephar://ainsi que les wrappers similaires avec des motifs explicites. - Recherchez dans votre base de code
IOFactory::loadetReader::load, puis remontez chaque appel jusqu'à la source du chemin. Tout appel accessible depuis un nom de fichier fourni par l'utilisateur, un nom d'upload ou une URL est celui qui compte. Épinglez la version corrigée avant que cet endpoint ne soit redéployé. - Ajoutez une défense en profondeur au niveau de la couche PHP. Désactivez les wrappers de flux
phar://,php://,data://etexpect://là où votre code d'import s'exécute, et validez ou canonicalisez tout chemin avant qu'il n'atteigne un chargeur. Si vous êtes encore sur PHP 7.x, ce CVE est une raison supplémentaire de terminer cette migration. Le runtime lui-même a supprimé le point de désérialisation automatique dans PHP 8.
Si vous développez en PHP, une bonne habitude est de traiter chaque bibliothèque de gestion de fichiers comme hostile aux chemins non fiables par défaut, comme nous le faisons lorsque nous concevons et auditons des services backend pour des clients ayant de vraies exigences de conformité. Le coût de cette habitude est quelques minutes par dépendance. Le coût de l'ignorer, c'est une RCE dans votre pipeline de documents.
Si l'idée qu'une bibliothèque tierce accède silencieusement à phar:// dans votre pile PHP vous semble être un problème que vous préféreriez détecter avant un attaquant, contactez-nous.