
Correção que preserva o comportamento para a injeção de cabeçalho HTTP CVE-2025-60876 no wget do BusyBox, com prova de conceito, patch de codificação percentual e artefatos de submissão upstream.
Uma correção que preserva o comportamento para CVE-2025-60876, uma vulnerabilidade de injeção de cabeçalho HTTP no wget do BusyBox, além da prova de conceito que a demonstra e dos artefatos para submeter a correção upstream ao BusyBox.
O wget do BusyBox copia o caminho e a query da URL para a linha de requisição HTTP literalmente. Uma URL que contenha um CR bruto (0x0D), LF (0x0A) ou outro byte de controle pode dividir a linha de requisição e injetar cabeçalhos controlados pelo atacante. Um espaço (0x20) tem o mesmo efeito: ele quebra o enquadramento METHOD SP request-target SP HTTP/1.1.
Afetados: BusyBox até 1.37.0 (o pkgver distribuído em todos os ramos Alpine com suporte). Relatado na lista de discussão do BusyBox em agosto de 2025; CVE atribuído como CVE-2025-60876 (NVD CVSS 6.5, Médio).
Dois patches já foram propostos upstream (Takeuchi Yuma, 2025-08; Radoslav Kolev, 2025-11). Ambos rejeitam uma URL ofensora e chamam bb_error_msg_and_die. Isso interrompe a injeção, mas também rejeita um espaço simples, de modo que uma URL como http://example.org/foo bar que antes funcionava agora gera erro. Essa regressão é o motivo pelo qual a correção não foi adotada downstream. A Alpine está segurando seu backport por causa disso (item de trabalho 17872).
Esta correção trata o caminho e o host de forma diferente, acompanhando o comportamento do GNU wget e do curl.
Caminho: percent_encode_target() em networking/wget.c faz percent-encoding dos bytes de controle (0x00 a 0x1f), espaço (0x20) e DEL (0x7f) no request-target antes de a linha de requisição ser montada:
http://example.org/foo bar é enviado como /foo%20bar, acompanhando GNU wget e curl. Sem regressão.% existente é deixado inalterado, de modo que um caminho já codificado não é duplamente codificado.Host: os mesmos bytes são rejeitados no host da URL. Um nome de host não pode legitimamente conter caracteres de controle ou um espaço, e o percent-encoding não é definido para o componente de autoridade. Isso importa no modo proxy: o host é colocado no request-target em forma absoluta (GET http://host/path) e no cabeçalho Host:, mas não é resolvido localmente, então um CR ou LF bruto no host poderia, de outra forma, injetar. O GNU wget (desde CVE-2017-6508) e o curl também rejeitam caracteres de controle no host.
A mudança está confinada a networking/wget.c e adiciona um helper.
Fora do escopo: o canal de controle FTP (ftpcmd envia target->path e o usuário/senha em bruto, um TODO reconhecido upstream) é uma classe de injeção separada, não faz parte de CVE-2025-60876 e não é tratado aqui. Deve ser rastreado separadamente.
Compilado contra BusyBox 1.37.0 com a mesma configuração que a Alpine usa (CONFIG_WERROR e CONFIG_TC desligados). Um listener local captura os bytes exatos que o wget coloca na rede.
| URL de entrada | Vanilla 1.37.0 | Corrigido |
|---|---|---|
/x + CRLF + Evil: injected | Evil: injected chega como um cabeçalho real (vulnerável) | GET /x%0D%0AEvil:%20injected HTTP/1.1, nenhum cabeçalho injetado |
/foo bar | envia um espaço literal | GET /foo%20bar HTTP/1.1 (sem regressão) |
/foo%20bar (já codificado) | inalterado | GET /foo%20bar HTTP/1.1 (não %2520) |
/normal | inalterado | GET /normal HTTP/1.1 (inalterado) |
proxy, host = h + CRLF + PInjected: 1 | PInjected: 1 chega como um cabeçalho real (vulnerável) | morre com bad character in URL host, nada é enviado |
proxy, host = example.test | normal | GET http://example.test/p HTTP/1.1 (inalterado) |
Captura completa: test/poc-output.txt. Os quatro testes testsuite/wget do BusyBox passam todos contra a compilação corrigida (test/testsuite-output.txt); as outras falhas da suíte completa observadas ali não estão relacionadas (mount/taskset precisam de root, e algumas peculiaridades conhecidas de contêineres restritos). Impacto de tamanho no aarch64 defconfig é de +185 bytes (wget_main +142, .rodata +43, nenhum símbolo adicionado ou removido), medido com scripts/bloat-o-meter e registrado em cover-letter.txt.
| Arquivo | O que é |
|---|---|
wget-cve-2025-60876.patch | A correção como um git format-patch contra o networking/wget.c do BusyBox, pronta para envio |
commit-message.txt | A mensagem de commit (também o cabeçalho do patch) |
cover-letter.txt | A carta de apresentação [PATCH v3] para a lista de discussão que reativa a thread existente |
apply_fix.py | Gerador que aplica a mudança a um networking/wget.c (usado para produzir o patch) |
test/serve_once.py | Listener de conexão única que ecoa a requisição HTTP bruta |
test/driver.sh | Compila BusyBox vanilla e corrigido e executa o PoC antes/depois, direto + proxy |
test/finalize.sh | Execução completa: PoC antes/depois, bloat-o-meter, testsuite/wget, e regenera o patch |
test/poc-output.txt | Bytes de requisição capturados antes/depois (direto e via proxy) |
test/testsuite-output.txt | Resultados do testsuite/wget do BusyBox contra a compilação corrigida |
test/finalize.sh é executado dentro de um contêiner alpine:edge com Docker e reproduz tudo (PoC, tamanho, testsuite, patch):
docker run --rm -v "$PWD:/work" alpine:edge sh /work/test/finalize.sh
Ele baixa o BusyBox 1.37.0, compila a versão vanilla (mostrando a injeção diretamente e via proxy), aplica a correção, recompila, reproduz os casos de requisição acima, executa bloat-o-meter e a suíte de testes do wget, e escreve o patch. test/driver.sh é uma variante mais leve, apenas com o PoC.
BusyBox é um projeto baseado em lista de discussão. A correção vai para [email protected] via git send-email, como uma resposta [PATCH v3] na thread de novembro de 2025 de Radoslav Kolev, creditando os relatores e revisores anteriores. Veja cover-letter.txt. A correção é GPLv2, a mesma licença do BusyBox.