Este é um desafio de desenvolvimento seguro que demonstra uma vulnerabilidade real de vazamento de memória não inicializada em uma API Rust. O cenário simula um serviço de echo de alta performance que implementou um padrão de reutilização de buffers para otimização, mas falhou na execução segura.
A API utiliza um padrão comum em sistemas de alta performance: reutilização de buffers. Para evitar alocações constantes de memória, a equipe implementou um sistema que reutiliza buffers pré-alocados. O erro fatal foi assumir que poderia simplesmente resetar o tamanho do buffer para sua capacidade máxima sem limpar adequadamente o conteúdo anterior, causando vazamento de dados sensíveis.
- Exposição de Dados de Sessão: Tokens JWT e IDs de usuário
- Vazamento de Informações de Autenticação: Dados de login e permissões
- Informações de Sistema: Dados residuais de operações anteriores
- Violação de Confidencialidade: Acesso não autorizado a dados privados de outros usuários
Você é um pentester contratado para avaliar a segurança de uma nova API de echo. O cliente suspeita que há uma vulnerabilidade que pode expor dados sensíveis. Sua missão é identificar e explorar essa falha para demonstrar o impacto real.
- Reconhecimento: Entender como a API funciona
- Identificação: Descobrir a vulnerabilidade de vazamento
- Exploração: Fazer requisições que maximizem o vazamento
- Extração: Decodificar os bytes vazados para encontrar dados sensíveis
- Documentação: Registrar o processo e impacto
- Docker
- curl
- Conhecimento básico de HTTP
# Clonar o repositório
git clone https://github.com/seu_usuario/echo_desatento.git
cd echo_desatento
docker build -t desafio-rust .
# Executar o container
docker run -p 8080:8080 --rm desafio-rustOpção 2: Setup com Rust Direto
# Clonar o repositório
git clone https://github.com/seu_usuario/echo_desatento.git
cd echo_desatento
# Executar diretamente (requer Rust instalado)
cargo runA API estará disponível em http://localhost:8080
GET /health- Verificar status da APIPOST /echo- Endpoint vulnerável (aceita JSON)
Vamos começar com uma requisição normal para entender o comportamento da API:
curl -s http://localhost:8080/echo \
-X POST \
-H "Content-Type: application/json" \
-d '{"message":"test"}' | ./decode_curl_output.shNota: A resposta vem como um array de bytes. Usamos o script
decode_curl_output.shpara torná-la legível.
Agora vamos tentar uma requisição deixando alguns headers em branco:
curl -s http://localhost:8080/echo \
-X POST \
-H "Content-Type: application/json" \
-H "User-Agent:" \
-d '{"message":"x"}' | ./decode_curl_output.shAnalise as respostas procurando por:
- Tokens JWT de sessão
- IDs de usuário e dados de autenticação
- Fragmentos de dados de sessão anteriores
- Padrões de dados que não deveriam estar na resposta
Dica Nível 1: Onde está o problema?
A vulnerabilidade está na forma como o comprimento (`len`) do buffer de resposta é definido após os dados da requisição serem copiados para ele. Examine a função `handle_request_with_reused_buffer` no arquivo `src/main.rs`.Dica Nível 2: Qual é o contrato quebrado?
Examine o bloco `unsafe`. O método `set_len` tem um contrato de segurança muito estrito: você, o programador, garante a Rust que todos os bytes até o novo comprimento estão devidamente inicializados. Esse contrato está sendo cumprido?Dica Nível 3: O que acontece com a memória não inicializada?
Quando você define `buffer.set_len(MAX_BUFFER_SIZE)`, mas só copiou `bytes_to_copy` bytes, o que acontece com os bytes restantes? Eles contêm dados residuais de operações anteriores na memória.Solução 1: Corrigindo o Bloco unsafe
Se o uso de unsafe fosse absolutamente necessário por motivos de performance, a correção seria garantir que o len do buffer reflita exatamente a quantidade de bytes que foram inicializados.
Correção:
Mude buffer.set_len(MAX_BUFFER_SIZE); para buffer.set_len(bytes_to_copy);.
Por quê? Assim, garantimos o contrato do set_len. Rust agora sabe o tamanho real dos dados válidos e não irá ler a memória não inicializada adjacente.
Solução 2: A Forma Idiomática e Segura (Sem unsafe)
Como discutido, o uso de unsafe deve ser a última alternativa. Em 99% dos casos, existe uma forma segura e quase tão performática de se atingir o mesmo objetivo. Neste cenário, a melhor solução é abandonar a otimização manual e deixar o Rust gerenciar a memória.
Correção Ideal:
A função handle_request_with_reused_buffer pode ser reescrita sem unsafe algum, simplesmente criando um Vec<u8> a partir dos dados de entrada.
fn handle_request_safely(input_data: &[u8]) -> Vec<u8> {
// Deixa Rust fazer o que faz de melhor: gerenciar memória de forma segura.
// O compilador otimiza isso muito bem.
input_data.to_vec()
}Benefícios:
- Elimina completamente a classe do bug
- Código mais legível e manutenível
- Performance similar (o compilador otimiza muito bem)
- Segurança garantida pelo sistema de tipos do Rust
- Experimente diferentes tamanhos de entrada
- Teste com diferentes headers HTTP
- Analise como a vulnerabilidade se comporta
- Implemente as correções sem quebrar o funcionamento da API
- Compare o comportamento antes e depois da correção
- Desafio Extra: Implemente ambas as soluções e compare a performance
ATENÇÃO: Este projeto é APENAS para fins educacionais. A vulnerabilidade foi intencionalmente implementada para demonstrar conceitos de segurança. Nunca use este código em produção ou em sistemas reais.