PoC: modelscan 0.8.8 multi-pickle legacy scan bypass (scanner evasion)
Coordinated-disclosure proof-of-concept submitted to huntr. The model file in this repository is a benign test artifact: its payload only runs
touch MP_legacy_pwn(creates an empty marker file in the working directory). No network access, no destructive action. Do not load untrusted model files.
What this demonstrates
modelscan 0.8.8 reports evil_legacy.pt as clean ("No issues found! π", exit 0), yet
torch.load("evil_legacy.pt", weights_only=False) executes the payload. The identical
os.system operator in a single-stream pickle is flagged β so this is a scan-coverage
correctness defect, not a denylist gap.
This is a scanner-evasion finding (huntr "unique methods to bypass our automated
scanners"), not a report of pickle deserialization itself. Root cause: scan_pytorch()
(modelscan/tools/picklescanner.py) calls scan_pickle_bytes(..., multiple_pickles=False),
so _list_globals() breaks after the first of the file's sequential pickle streams and
never inspects the model object graph.
How the PoC model was created
A well-formed 5-stream PyTorch legacy file: magic number, protocol, sys_info, the
os.system payload object, and an empty storage-keys list (so torch.load returns
without error β a valid file, not malformed). See poc.py.
Files
evil_legacy.ptβ the malicious-but-benign model file (modelscan-clean, executes on load).poc.pyβ self-contained: builds the file, scans it (clean), loads it (executes), and runs a single-stream control (flagged). One command.
Reproduce
pip install "modelscan==0.8.8" torch
modelscan -p evil_legacy.pt # -> No issues found! π (exit 0)
python -c "import torch; torch.load('evil_legacy.pt', weights_only=False)" # -> creates MP_legacy_pwn
python poc.py # full differential, prints: RESULT: PASS
Affected / fix
modelscan 0.8.8 (latest at disclosure). Fix: pass multiple_pickles=True in the PyTorch
legacy scan path (scan_pytorch), matching the non-legacy behaviour.