| --- |
| license: mit |
| tags: |
| - security-poc |
| - coordinated-disclosure |
| --- |
| |
| # 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. |
|
|