
Laboratorio de contrabando de peticiones HTTP: Apache 2.4.55 inyección CRLF
| Componente | Rol | Versión |
|---|---|---|
| Apache HTTP Server | Reverse Proxy | 2.4.55 (vulnerable) |
| Spring Boot (Tomcat embebido) | Backend API | 4.x (Java 21) |
| SQLite | Base de datos | — |
Usuario ──► Apache :80 (Proxy) ──► Spring Boot :8080 (Backend) ──► SQLite
│
├─ mod_rewrite + mod_proxy
├─ CVE-2023-25690: CRLF no 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 el registro de usuarios en la base de datos
POST /public/login: permite el inicio de sesión mediante la verificación de las credenciales introducidas y emite un token de sesión
GET /public/dashboard: área reservada de los usuarios
GET /api/status: acepta el parámetro "name", es un endpoint de ejemplo para la verificación del estado de los servicios
GET /public/logout
POST /admin/edit/{id}/{newName}/{newPass}: es una ruta teóricamente inaccesible al público que permite a los administradores modificar los datos de los usuarios
Durante la actividad de penetration testing se identificó una vulnerabilidad crítica en la infraestructura de reverse proxy que expone el backend Spring Boot. El proxy Apache HTTP Server versión 2.4.55 está afectado por la vulnerabilidad CVE-2023-25690 (HTTP Request Smuggling), que permite a un atacante eludir los filtros de seguridad impuestos en el proxy y alcanzar directamente endpoints administrativos internos no protegidos.
El ataque aprovecha la ausencia de sanitización de los caracteres de control (CRLF) en las RewriteRule de Apache, permitiendo la inyección de una segunda petición HTTP entre los parámetros de una petición legítima hacia el backend. La prueba de concepto demostró la modificación no autorizada de las credenciales de usuario en la base de datos a través del endpoint /admin/edit/{id}/{newName}/{newPass}, teóricamente protegido por las ACL del proxy.
Recomendaciones: Actualizar inmediatamente Apache HTTP Server a la versión ≥ 2.4.56, reforzar los filtros de seguridad en el proxy e implementar una capa de seguridad en el backend (Spring Security) para todos los endpoints sensibles.
Identificación de la versión de Apache mediante el análisis de los headers HTTP de la respuesta.
$ 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: El header del servidor revela Apache/2.4.55. Consulta de la base de datos de CVEs → correspondencia con CVE-2023-25690.
Según la CVE, en esta versión de Apache, si existe una RewriteRule que copia en la URL de destino del backend caracteres genéricos provenientes de la petición al proxy, el texto transcrito no se sanitiza, por lo que también pasan caracteres de control (como los retornos de carro).
Por ejemplo: RewriteRule "^/here/(.*)" "http://backend.com:8080/elsewhere?$1" [P] // la P indica modo proxy
Por lo tanto, ahora nuestro objetivo es descubrir un posible endpoint que realice esta transcripción a nivel de proxy.
Del análisis de las respuestas y del comportamiento de la aplicación se observa que la sesión se gestiona mediante JSESSIONID, lo que confirma el uso de un Servlet Container Java (como Apache Tomcat, Jetty o WildFly). Además, la petición a endpoints inexistentes devuelve una "Whitelabel Error Page", lo que indica que en el backend hay Spring Boot.
Mediante un script bash para la automatización del fuzzing con diccionario se mapearon los endpoints expuestos en la red (presumiblemente todos).
Resultado:
| Endpoint | Código HTTP | Método | Parámetros |
|---|---|---|---|
| admin | 403 | GET | (sin parámetros) |
| public/register | 200 | POST | user=test&pass=test |
| public/login | 200 | POST | user=test&pass=test |
| public/dashboard | 200 | GET | (sin parámetros) |
| public/logout | 200 | GET | (sin parámetros) |
| api/status | 200 | GET | (sin parámetros) |
| service/* | 200 | GET | (sin parámetros) |
Dado que
devuelven ambas la misma respuesta, se entiende que apuntan al mismo endpoint del backend. Además, dado que peticiones como /service/x/y/z (que muy probablemente no existen) no devuelven 404, se puede deducir que el endpoint original acepta un parámetro y no una path variable. Por lo tanto, en conclusión, se puede deducir que las peticiones a /service/<servicio> se traducen mediante una RewriteRule hacia el backend Spring Boot (justo lo que buscábamos). Ahora hay que determinar si esta RewriteRule es dummy, es decir, si usa una regex tipo .* o si está bien estructurada.
Intento insertar caracteres de control en la petición para dividir el contenido legítimo del 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 ... El servicio 'x' está operativo y estable.
He insertado un parámetro personalizado para verificar que los CRLF se interpretan correctamente.
trash_header tiene la función de encapsular los headers que Apache insertará en la petición al backend (de este modo se interpretarán como simple texto de X-Header y no tendrán valor a efectos de la petición HTTP).
Con tcpdump en el contenedor del backend pude interceptar la petición HTTP proveniente de Apache:
docker exec -it apache_vuln-spring-backend-1 sh
apk add tcpdump
tcpdump -i any -A port 8080
El backend ve esta petición:
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
"Los caracteres de control han controlado"
La respuesta me indica que la parte con los caracteres de control ha pasado sin problemas como estructura de la propia petición HTTP y no simplemente como parámetro (ya que el nombre interceptado por el backend es solo 'x'). Por lo tanto, hemos impuesto el formato de la petición HTTP hacia el backend y el proxy lo ha aceptado; esto abre el camino al payload real para el smuggling.