Retour au blog
CVE-2026-34084PhpSpreadsheetPHPPHAR deserializationRCESSRFCVE

PhpSpreadsheet CVE-2026-34084 : RCE via phar:// dans IOFactory::load()

PhpSpreadsheet (toutes branches < 5.6.0) permet RCE et SSRF via phar://, ftp:// et ssh2.sftp:// dans IOFactory::load(). Patch et hardening pour PHP.

11 mai 20265 min de lecture

PhpSpreadsheet est l'une des bibliothèques PHP les plus déployées au monde : utilisée par des millions de sites pour importer/exporter Excel, OpenDocument et CSV. CVE-2026-34084 (CVSS 9.8) révèle que la fonction IOFactory::load() accepte des chemins wrappers PHP (phar://, ftp://, ssh2.sftp://) qui passent le contrôle File::assertFile(). Le wrapper phar:// déclenche la désérialisation des métadonnées PHAR — chemin d'attaque classique vers une RCE PHP. Les wrappers ftp:// et ssh2.sftp:// ouvrent eux la voie à du SSRF côté serveur.

Pour tout site qui passe une donnée utilisateur (chemin de fichier, URL d'import, paramètre d'upload) à IOFactory::load(), c'est un boulevard pour la compromission complète.


Détails techniques

Composant vulnérable

La méthode IOFactory::load(string $filename, ...) est le point d'entrée standard pour charger un fichier tableur. Elle commence par appeler File::assertFile($filename) pour vérifier que le chemin est valide. Cette vérification utilise is_file()qui retourne true pour les chemins wrapper PHP comme phar://, ftp://, ssh2.sftp://.

Une fois la vérification passée, PhpSpreadsheet ouvre le fichier via les fonctions PHP standard. Si le chemin est phar://archive.phar, PHP désérialise automatiquement les métadonnées de l'archive PHAR, ce qui invoque potentiellement des méthodes magiques (__wakeup, __destruct) sur des objets dont la classe est définie dans le code de l'application.

C'est le pattern classique de gadget chain PHP : si l'application contient une classe avec une méthode magique exploitable (ex: eval($this->code) dans __destruct), l'attaquant exécute du code arbitraire.

Caractéristiques

ChampValeur
CVSS 3.19.8 (CRITICAL)
VecteurAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWECWE-502 (Deserialization of Untrusted Data) + CWE-918 (SSRF)
Pré-requisLe paramètre $filename de IOFactory::load() doit être influençable par l'utilisateur
Pré-requis #2 pour RCEPrésence d'une gadget chain dans le code (souvent fournie par les frameworks Laravel/Symfony/Yii/CodeIgniter et leurs dépendances)

Produits et versions affectés

BrancheVersions affectéesVersion corrigée
1.x≤ 1.30.21.30.3
2.0.x2.0.0 → 2.1.142.1.15
2.2.x → 2.4.x2.2.0 → 2.4.32.4.4
3.3.x → 3.10.x3.3.0 → 3.10.33.10.4
4.x → 5.x4.0.0 → 5.5.05.6.0

Vérifiez votre version :

composer show phpoffice/phpspreadsheet | grep versions

Exploitation et impact

Scénarios d'attaque

Scénario 1 — Import Excel utilisateur

Une application permet aux utilisateurs d'importer un fichier Excel pour traitement. Le contrôleur reçoit le path stocké et appelle IOFactory::load($uploadedFile). L'attaquant uploade un fichier d'apparence légitime mais nommé evil.phar (ou via path traversal force le wrapper phar://).

Scénario 2 — Paramètre URL contrôlé

L'application accepte un paramètre ?file=... pour générer un rapport à partir d'un template. L'attaquant fournit ?file=phar:///tmp/uploaded.phar et déclenche la désérialisation.

Scénario 3 — SSRF via ftp/ssh2.sftp

Même sans gadget chain RCE, un attaquant peut utiliser ftp://internal-service:21/file.xlsx pour faire émettre des requêtes par le serveur PHP vers des services internes inaccessibles depuis Internet — typique pour attaquer une instance Redis, un metadata service cloud (AWS IMDS), ou un dashboard interne.

PoC concept (RCE via phar)

// Côté attaquant — fabrication du PHAR avec gadget chain
$phar = new Phar('evil.phar');
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($gadgetChainObject);  // gadget de l'application cible
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
// Le fichier evil.phar est ensuite uploadé sur la cible
// Côté cible (vulnérable)
$file = $_GET['file'];  // attacker controlled
\PhpOffice\PhpSpreadsheet\IOFactory::load($file);
// si $file = 'phar:///tmp/evil.phar', la gadget chain s'exécute

Conséquences

  • RCE complète dans le processus PHP (souvent www-data ou compte applicatif)
  • SSRF vers les services internes non exposés
  • Lecture de fichiers via phar:// pointant sur des chemins arbitraires
  • Pivot LAN depuis le serveur web

Détection et IOC

Logs serveur web

Recherchez dans les access logs des paramètres contenant phar://, ftp://, ssh2.sftp:// :

grep -E "phar://|ftp://|ssh2\.sftp://" /var/log/apache2/access.log

Logs PHP

Activez le logging des erreurs et chassez :

  • Warnings de désérialisation
  • Erreurs PharException
  • Fatal errors __wakeup, __destruct
grep -E "Phar|unserialize|__wakeup|__destruct" /var/log/php/error.log

Audit du code applicatif

Localisez tous les appels à IOFactory::load() :

grep -rn "IOFactory::load" /var/www/your-app/

Pour chaque appel, vérifiez :

  • Le $filename est-il dérivé d'une entrée utilisateur (POST, GET, COOKIE, header) ?
  • Y a-t-il une validation explicite du préfixe (rejet de phar://, etc.) ?

Règle WAF (ModSecurity)

SecRule ARGS "@rx (phar|ftp|ssh2\.sftp)://" \
  "id:'2026034084',phase:2,deny,status:403,\
   msg:'PhpSpreadsheet CVE-2026-34084 wrapper injection attempt',\
   t:lowercase"

Mitigation et patch

Action immédiate : mise à jour

# Composer
composer require phpoffice/phpspreadsheet:^5.6.0
# Ou pour les branches plus anciennes :
composer require phpoffice/phpspreadsheet:^3.10.4
composer require phpoffice/phpspreadsheet:^2.4.4
composer require phpoffice/phpspreadsheet:^2.1.15
composer require phpoffice/phpspreadsheet:^1.30.3

Workaround si mise à jour impossible

  1. Validation côté application avant tout appel à IOFactory::load() :
function isSafeSpreadsheetPath(string $path): bool {
    // Refuser tous les wrappers PHP
    $forbidden = ['phar://', 'ftp://', 'ssh2.sftp://', 'http://', 'https://',
                  'data://', 'php://', 'zip://', 'compress.zlib://'];
    foreach ($forbidden as $prefix) {
        if (stripos($path, $prefix) === 0) return false;
    }
    // Forcer un chemin absolu dans un répertoire de confiance
    $real = realpath($path);
    return $real !== false && str_starts_with($real, '/var/uploads/');
}

if (!isSafeSpreadsheetPath($filename)) {
    throw new InvalidArgumentException("Unsafe path");
}
\PhpOffice\PhpSpreadsheet\IOFactory::load($filename);
  1. Désactiver l'extension PHAR dans php.ini si elle n'est pas utilisée par votre application :
disable_functions = phar_loader
; ou supprimer l'extension phar du chargement
  1. WAF avec règle de filtrage des wrappers PHP en entrée

Hardening durable

  • Auditer toutes les bibliothèques PHP qui appellent is_file(), file_exists(), file_get_contents() sur des chemins user-controlled (PhpSpreadsheet n'est pas seul à avoir ce pattern)
  • Bloquer au niveau Apache/Nginx les requêtes contenant phar://, ftp://, etc. dans les paramètres
  • Faire tourner les credentials applicatifs si vous suspectez une exploitation passée
  • Auditer les logs PHP pour des warnings de désérialisation anciens

Pourquoi surveiller en continu vos dépendances applicatives

PhpSpreadsheet est une de ces dépendances qu'on installe une fois et qu'on oublie — pourtant elle est embarquée dans des milliers de projets PHP, incluant des CMS, des outils internes, des plugins WordPress. Une CVE comme CVE-2026-34084 affecte instantanément l'ensemble de ces déploiements, mais peu de DSI ont une visibilité sur leurs dépendances tierces.

Avec cveo.tech, inventoriez vos applications PHP et leurs versions de dépendances clés et soyez alerté automatiquement dès qu'une CVE critique cible une de vos versions exactes — y compris sur les composants enfouis dans le composer.lock que personne ne regarde plus.

Surveillez les CVE avec l'IA

Recherche IA, scoring CVSS, surveillance de parc et alertes automatiques.