--- library_name: mlflow tags: - security-research - modelscan-bypass - copyreg - ext2 - pickle-registry - rce --- # ModelScan EXT2 Opcode Bypass — RCE via Pickle Extension Registry ## Summary ModelScan's pickle scanner only inspects **GLOBAL**, **INST**, and **STACK_GLOBAL** opcodes to detect dangerous module/function references. It completely ignores **EXT1**, **EXT2**, and **EXT4** opcodes. This MLflow model uses `copyreg.add_extension()` to register `os.system` in pickle's extension registry, then loads it via the **EXT2** opcode — which ModelScan never examines. The result: arbitrary OS command execution with zero detection. ## Attack Chain 1. `copyreg.add_extension('os', 'system', 31337)` registers os.system under external code 31337 2. `EXT2 31337` opcode loads os.system from the registry — **invisible to ModelScan** 3. REDUCE calls `os.system('cmd')` — command executed ## Why ModelScan Fails ModelScan analyzes only three opcode types: - `GLOBAL` → `module\nname\n` - `INST` → `module\nname\n` - `STACK_GLOBAL` → walks backwards for string operands **EXT1, EXT2, and EXT4 opcodes are completely absent from the scanner code.** The dangerous `os.system` reference is loaded through the extension registry and never appears as a module/name pair in the opcode stream. ## Verification ```bash # ModelScan says CLEAN modelscan -p model.pkl # Output: No issues found! 🎉 # pickle.load() triggers RCE python3 -c " import pickle with open('model.pkl', 'rb') as f: pickle.load(f) " # Output: EXT2_PWNED ``` ## Impact - **Severity**: Critical (CVSS 9.8) - **Novel technique**: Not covered in any known CVE, GHSA, or security advisory - Remote, no auth needed, no user interaction - Scissors past all unsafe_globals restrictions