높음취약점
MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False safety control bypassed by mlflow.statsmodels flavor — RCE via crafted model artifact
GHSA-gqvg-gmmx-x4hm · 갱신 2026. 9. 2.
영향 받는 패키지
| 생태계 | 패키지 | 수정 버전 |
|---|---|---|
| PyPI | mlflow | 3.15.0 |
GHSA-gqvg-gmmx-x4hm · 갱신 2026. 9. 2.
| 생태계 | 패키지 | 수정 버전 |
|---|---|---|
| PyPI | mlflow | 3.15.0 |
statsmodels.iolib.api.load_pickle is a thin wrapper around pickle.load. Its own docstring warns: "Never unpickle data received from an untrusted or unauthenticated source."
An attacker crafts an MLmodel YAML that specifies mlflow.statsmodels as the loader module:
flavors:
python_function:
loader_module: mlflow.statsmodels
data: model.pkl
statsmodels:
data: model.pkl
statsmodels_version: 0.14.0
With a malicious model.pkl placed alongside it in the artifact store, any call to:
os.environ["MLFLOW_ALLOW_PICKLE_DESERIALIZATION"] = "False"
mlflow.pyfunc.load_model("models:/MaliciousModel/1")
...deserializes the pickle file with no guard check, executing arbitrary code with the privileges of the calling process.
On default MLflow deployments (no --app-name basic-auth), authentication is disabled, so artifact upload requires no credentials.
mlflow/statsmodels/__init__.py L307-310: _load_model — calls smio.load_pickle without checking MLFLOW_ALLOW_PICKLE_DESERIALIZATIONmlflow/statsmodels/__init__.py L313-320: _load_pyfunc — dispatches to _load_model without checking the controlPermalink (commit 0b0c576c):
Add the missing guard to mlflow/statsmodels/__init__.py:
from mlflow.environment_variables import MLFLOW_ALLOW_PICKLE_DESERIALIZATION
from mlflow.utils.databricks_utils import (
is_in_databricks_model_serving_environment,
is_in_databricks_runtime,
)
def _load_model(path):
if (
not MLFLOW_ALLOW_PICKLE_DESERIALIZATION.get()
and not is_in_databricks_runtime()
and not is_in_databricks_model_serving_environment()
):
raise MlflowException(
"Deserializing model using pickle is disallowed, but this statsmodels "
"model requires pickle deserialization. Set environment variable "
"'MLFLOW_ALLOW_PICKLE_DESERIALIZATION' to 'true' to allow this."
)
import statsmodels.iolib.api as smio
return smio.load_pickle(path)