Статья CRLF Injection: от HTTP Response Splitting до захвата сессий — эксплуатация и реальные CVE

Запечатанный конверт с восковой печатью разрывается по сгибу, обнажая скрытый HTTP-заголовок Set-Cookie внутри. Криминалистическая макросъёмка в десатурированной палитре с красным акцентом на слома...


На bug bounty в финтехе я потратил три минуты на обнаружение CRLF injection в эндпоинте редиректа - и два часа на эскалацию до XSS через HTTP response splitting с захватом сессии администратора. Триажер поставил Medium, хотя цепочка вела до полного session hijacking через подставленный Set-Cookie. Пришлось писать развёрнутый комментарий с видео-PoC, чтобы severity пересмотрели. CRLF injection не входит в OWASP Top 10 как отдельный класс - она подпадает под A03:2021 Injection - но при грамотной эскалации два инжектированных байта (\r\n) превращаются в полноценный захват сессии. Разберём механику от байт-последовательности до kill chain на реальных CVE.

Место CRLF injection в цепочке атаки​

CRLF injection - не финальная цель, а точка входа. Сама по себе она стоит копейки на любой bug bounty платформе. Ценность появляется, когда из неё вырастает цепочка. По классификации MITRE ATT&CK она покрывает несколько тактик в зависимости от вектора эскалации:

Этап kill chainMITRE ATT&CKРоль CRLF injection
Initial AccessExploit Public-Facing Application (T1190)Инъекция через параметр публичного веб-приложения
Initial Access / C2Content Injection (T1659)Внедрение произвольного контента в HTTP-ответ
ExecutionJavaScript (T1059.007)Выполнение JS через XSS из response splitting
Credential AccessSteal Web Session Cookie (T1539)Кража cookie инжектированным JavaScript
Credential AccessWeb Cookies (T1606.001)Подделка cookie через инъекцию Set-Cookie
CollectionBrowser Session Hijacking (T1185)Перехват активной сессии пользователя
Lateral MovementWeb Session Cookie (T1550.004)Использование украденной сессии для доступа к ресурсам
ImpactTransmitted Data Manipulation (T1565.002)Модификация HTTP-ответа при cache poisoning

Типичная цепочка: CRLF injection в заголовок ответа → инъекция Set-Cookie для session fixation (T1606.001), либо response splitting с XSS (T1059.007) для кражи cookie (T1539) → lateral movement через украденную web-сессию (T1550.004). Именно выбор вектора эскалации определяет итоговый severity. Голый CRLF без цепочки на большинстве платформ - Low или Informational. С цепочкой до session hijacking - совсем другой разговор.

Механика инъекции: кодировки, серверы и обходы​

HTTP/1.1 использует последовательность CR (ASCII 13, \r) + LF (ASCII 10, \n) как разделитель заголовков. Одиночный CRLF разделяет заголовки между собой, двойной CRLF (\r\n\r\n) отделяет заголовки от тела ответа. По CWE-93 (Improper Neutralization of CRLF Sequences) уязвимость возникает, когда приложение не нейтрализует CRLF-последовательности из пользовательского ввода. CWE-74 (Injection) описывает более широкий класс: приложение формирует структуру данных из внешнего ввода без фильтрации спецсимволов - и атакующий модифицирует данные, обходит защиту, меняет логику выполнения.

Кодировки и обход фильтров​

Символы \r\n в raw-виде через стандартный HTTP-клиент или WAF не пройдут. Рабочий вариант зависит от контекста:

КодировкаЗаписьГде работаетГде не работает
URL-encoded%0d%0aGET-параметры, query stringТело POST с application/json
Double URL-encoded%250d%250aПрокси-цепочки с двойным декодированиемОдноуровневое декодирование
Unicode overlong%c0%8d%c0%8aУстаревшие парсеры UTF-8Современные фреймворки
Bare LF%0aNode.js http module (ряд версий)Apache, Nginx (требуют полный CRLF)
Null byte + CRLF%00%0d%0aПриложения с truncation по nullЯзыки без C-string семантики

Принципиальная разница между %0d%0a и текстовым \r\n: первое - URL-encoded управляющие символы, которые веб-сервер декодирует перед передачей значения в приложение. Если приложение вставляет декодированное значение в HTTP-заголовок без санитизации - CRLF injection состоялась. Второе - четыре печатных символа, не управляющие коды. Путаница между ними - частая причина ложноотрицательных тестов.

Различия обработки CRLF на разных серверах​

Поведение при встрече CRLF в пользовательском вводе зависит от стека. Это критически важно при выборе payload - то, что работает на Node.js, может не пройти на Apache, и наоборот.

Node.js (http module) - исторически принимал bare LF (\n) как разделитель заголовков, что расширяло поверхность атаки. В ряде версий http.request() позволял инъекцию через заголовки, что привело к нескольким CVE в экосистеме. Актуальные версии Node.js фильтруют CRLF в res.setHeader(), но ручное формирование ответа через res.socket.write() остаётся уязвимым. И именно этот паттерн встречается в legacy-коде чаще, чем хотелось бы.

Nginx - при использовании как reverse proxy с директивой proxy_set_header и подстановкой переменных $arg_* может передать CRLF-последовательности на бэкенд, если значение не проходит фильтрацию через map. Сам Nginx при генерации ответа CRLF в заголовках не допускает - тут он молодец.

Apache - mod_headers экранирует CRLF при добавлении заголовков через Header set. Уязвимость сохраняется при использовании ErrorDocument с пользовательскими данными или mod_rewrite с инъекцией в заголовок Location. Второй вариант я встречал на реальных проектах - кто-то написал RewriteRule с %{QUERY_STRING} прямо в Location, и привет.

Фреймворки (Express.js, Django, Spring Boot) - нейтрализуют CRLF в стандартных API для установки заголовков в актуальных версиях. Уязвимость появляется при ручном формировании HTTP-ответа через raw socket, использовании устаревших библиотек или прокси-слоёв, подставляющих пользовательские данные до фреймворка. Фреймворк защищает - но только если через него работают, а не мимо.

HTTP Response Splitting и захват сессии через CRLF​

Session fixation через Set-Cookie injection​

Простейший вектор - инъекция заголовка Set-Cookie в HTTP-ответ. Если приложение подставляет пользовательский ввод в заголовок (типично: Location при редиректе, Content-Disposition при скачивании файла, кастомные заголовки), атакующий добавляет CRLF и свой заголовок.

Пример: приложение формирует заголовок X-Custom-Name: <user_input>. Атакующий отправляет значение test%0d%0aSet-Cookie:%20session=attacker_value;%20Path=/. Если сервер не фильтрует CRLF, ответ содержит дополнительный заголовок Set-Cookie: session=attacker_value; Path=/. Браузер жертвы принимает подставленный cookie - session fixation готова. Атакующий уже знает значение сессии, которую будет использовать жертва. По MITRE ATT&CK - Web Cookies (T1606.001, Credential Access).

Один заголовок, два байта - и сессия зафиксирована. Дальше остаётся подождать, пока жертва залогинится.

XSS через response splitting​

Двойной CRLF (%0d%0a%0d%0a) завершает секцию заголовков и начинает тело ответа. Всё, что идёт после двойного CRLF, браузер интерпретирует как HTML/JavaScript.

По описанию из Acunetix, полноценное HTTP response splitting строится так:
Код:
http://target.example.com/page?param=value%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent-Length:%2025%0d%0a%0d%0a<script>alert(1)</script>
Логика: Content-Length: 0 заставляет браузер считать первый ответ завершённым. Далее начинается второй «ответ» с собственными заголовками и телом, содержащим JavaScript. Браузер выполняет скрипт - это XSS (T1059.007), который далее используется для кражи cookie (T1539) или перехвата сессии (T1185).

На практике полноценный response splitting с двумя «ответами» в modern стеках работает редко - серверы и прокси нормализуют заголовки. Но инъекция одного заголовка (Set-Cookie, Location, Access-Control-Allow-Origin) через одиночный CRLF работает значительно чаще. И этого хватает.

Web cache poisoning через CRLF​

Когда HTTP-ответ с инъекцией кэшируется промежуточным прокси (Varnish, Squid, CDN-edge), каждый последующий пользователь получает отравленный ответ. Один запрос атакующего через CDN может затронуть тысячи пользователей до истечения TTL кэша. По CWE-113 (Improper Neutralization of CRLF Sequences in HTTP Headers) cache poisoning - одно из основных последствий HTTP response splitting.

Но есть нюанс: cache poisoning через CRLF работает только если между клиентом и сервером стоит кэширующий прокси, этот прокси не нормализует HTTP-заголовки перед кэшированием, а приложение не фильтрует CRLF до формирования ответа. Без кэширующего прокси вектор ограничен единичной жертвой. Зато если прокси есть и он кэширует - масштаб поражения растёт на порядки.

CVE-2023-4767: CRLF в ManageEngine Desktop Central

Разбор реальной CVE показывает, как CRLF injection выглядит в production-коде корпоративного продукта. Не в учебном примере, а в системе, которая управляет тысячами рабочих станций.

CVE-2023-4767 - CRLF injection в ManageEngine Desktop Central версии 9.1.0 (Zoho Corp.). Уязвимый параметр - fileName в эндпоинте /STATE_ID/1613157927228/InvSWMetering.csv.

ПараметрЗначение
CVSS 3.16.1 (MEDIUM)
ВекторCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
CWECWE-93 (CRLF Injection), CWE-74 (Injection)
Затронутый продуктzohocorp:manageengine_desktop_central 9.1.0
ИсточникINCIBE (Spanish National Cybersecurity Institute)

Разбор CVSS-вектора по компонентам: AV:N - атака по сети, локальный доступ не нужен. AC:L - низкая сложность эксплуатации, никаких особых условий. PR:N - привилегии не требуются, эндпоинт доступен без аутентификации. UI:R - нужно действие пользователя (жертва кликает по ссылке). S:C - Changed scope: уязвимость в серверном компоненте влияет на другой security domain (браузер жертвы). C:L / I:L - низкий импакт на конфиденциальность и целостность (cookie manipulation, header injection). A:N - доступность не затронута.

Параметр fileName подставляется в HTTP-заголовок ответа (предположительно Content-Disposition) без нейтрализации CRLF. Атакующий формирует URL с %0d%0a в значении fileName, отправляет ссылку жертве (отсюда UI:R в векторе) - и инжектирует произвольные HTTP-заголовки в ответ сервера.

ManageEngine Desktop Central - корпоративная система управления конечными устройствами. CRLF injection в таком продукте - это Exploit Public-Facing Application (T1190, Initial Access): продукт выставлен в сеть, атака не требует аутентификации. Эскалация до session hijacking через подставленный Set-Cookie - вопрос одного дополнительного заголовка. А учитывая, что Desktop Central по своей природе имеет доступ ко всем управляемым хостам, импакт от угнанной админской сессии выходит далеко за рамки CVSS 6.1.

Обнаружение и эксплуатация CRLF injection​

Требования к окружению​

  • ОС: Kali Linux 2024+, Parrot OS, macOS или Windows с установленным Burp Suite
  • Инструменты: Burp Suite Community/Pro (Repeater), curl 7.80+, nuclei 3.x (опционально)
  • Сеть: доступ к целевому приложению (тестовая среда или скоуп bug bounty)
  • Привилегии: не требуются - CRLF injection в большинстве случаев эксплуатируется без аутентификации

Ручная проверка и автоматизация​

Первый шаг
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме


Для массовой проверки в скоупе bug bounty - nuclei с темплейтами из набора projectdiscovery/nuclei-templates (каталог http/vulnerabilities/crlf/). Nuclei отправляет запросы с CRLF-пейлоадами в параметры и проверяет наличие инжектированного заголовка в ответе. Для кастомных кейсов - ffuf с wordlist из CRLF-пейлоадов в разных кодировках, фильтрация ответов по наличию маркерного заголовка.

Где WAF ловит CRLF injection - и где промахивается​

Большинство WAF (ModSecurity с CRS, Cloudflare WAF, AWS WAF) детектируют стандартные %0d%0a в URL-параметрах - базовое правило, которое работает из коробки. Но ряд сценариев проходят мимо:

СценарийПочему WAF пропускает
CRLF в теле POST с Content-Type: application/jsonМногие WAF не разбирают JSON-тело на CRLF
Double encoding (%250d%250a) при наличии reverse proxyWAF декодирует один раз, апстрим - второй
CRLF в значении Cookie-заголовкаCookie parsing в WAF менее строгий
CRLF через WebSocket upgradeWAF может не инспектировать заголовки WebSocket handshake
Unicode overlong encodingУстаревшие WAF без Unicode-нормализации

На стороне серверных фреймворков - Express.js (res.setHeader()), Django (HttpResponse), Spring Boot (HttpServletResponse.setHeader()) нейтрализуют CRLF в актуальных версиях. Уязвимость живёт в ручном формировании HTTP-ответа через raw socket, устаревших версиях библиотек и middleware-слоях, подставляющих пользовательские данные в заголовки до фреймворка.

Отдельный вектор - log injection. WAF тут вообще не помощник - он защищает HTTP-ответы, а не серверные логи. Если приложение пишет пользовательский ввод в лог без фильтрации CRLF, атакующий подставляет ложные записи: в лог попадает строка с IP 127.0.0.1 вместо реального IP. Это не HTTP response splitting, но при пентесте используется для сокрытия следов - tampered log усложняет расследование.

По четырём годам в bug bounty могу сказать: CRLF injection встречается примерно в каждом десятом веб-приложении с legacy-кодом. Паттерн один и тот же - разработчики защищают SQL injection и XSS, но про заголовки забывают. CRLF injection подпадает под A03:2021 Injection в OWASP Top 10, но в чеклистах разработчиков отдельным пунктом фигурирует редко. Фреймворк защищает стандартные API, а кастомный middleware, формирующий Content-Disposition по имени файла из запроса, оставляет дыру размером в два байта.

Severity CRLF injection на bug bounty платформах систематически занижается. Триажеры видят %0d%0a в URL и ставят Low или Informational, не проверяя эскалацию. Я несколько раз показывал цепочку от CRLF до полного захвата сессии через cache poisoning - и severity пересматривали с Low на High. Мораль: нашли CRLF - не отправляйте голый PoC с одним инжектированным заголовком. Покажите полную цепочку до реального импакта: cache poisoning, session fixation, XSS. Любой из этих векторов поднимает severity и выплату. Если хочется посмотреть, как injection-цепочка от CRLF до захвата сессии собирается на живом стенде - web-задачи на HackerLab.pro построены именно на таких примитивах.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab