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

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

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

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

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

Категории

Все категории
Loading categories
poc-h2-CVE-2026-71554 — PoC для CVE-2026-71554 — примитив request smuggling с дублирующимся заголовком Host в h2 (исправлено в 4.4.1) | Kitploit
Инструменты/GitHubGitHub/sunandm/poc-h2-cve-2026-71554
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийТестирование безопасности APIВеб-безопасностьБезопасность APIСостязательная Атака
GitHubsunandm/poc-h2-cve-2026-71554

poc-h2-CVE-2026-71554

PoC для CVE-2026-71554 — примитив request smuggling с дублирующимся заголовком Host в h2 (исправлено в 4.4.1)

Репозиторий
1282 месяцев назадЕщё не проверено

Популярное

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

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

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

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

Смотреть все инструменты →
Поделиться

CVE-2026-71554 — примитив контрабанды запросов через дублирующийся заголовок Host в h2

Это моя первая CVE, и я опубликовал этот PoC, чтобы задокументировать находку и помочь другим понять и воспроизвести её.

CVE: CVE-2026-71554 GHSA: GHSA-6hr6-w5qg-qmwg Затронуто: h2 <= 4.4.0 Исправлено: h2 4.4.1 Уровень серьёзности: Средний (CWE-444, CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L = 5.3) NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-71554


Что я обнаружил

При изучении логики проверки заголовков в h2 я заметил, что _validate_host_authority_header() в src/h2/utilities.py проверяет совпадение Host и :authority, но сравнивает только последний увиденный заголовок Host. Если отправить два заголовка Host, h2 использует второй для проверки совпадения и без возражений передаёт оба приложению.

Что интересно, h2 4.4.0 уже отклоняет дублирующиеся заголовки Content-Length с помощью ProtocolError. То же исправление так и не было применено к Host. В исходном коде даже был комментарий TODO, признающий именно этот пробел:

# TODO: We should also guard against receiving duplicate Host headers,
#       and against sending duplicate headers.

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

_validate_host_authority_header() в src/h2/utilities.py использует цикл с принципом «побеждает последний», который запоминает последнее увиденное значение Host и проверяет только следующее:

  1. Присутствует хотя бы один из :authority или Host
  2. Когда присутствуют оба, они совпадают

Проверки количества заголовков Host нет. Каждый заголовок Host передаётся дальше приложению независимо от того, сколько их присутствует.


Сценарии атаки

Случай 1 — два заголовка Host, без :authority

Клиент отправляет:

:method: GET
:path: /
:scheme: https
host: good.internal
host: evil.attacker

h2 принимает оба. Приложение получает оба заголовка Host.

Понижение до HTTP/1.1 даёт:

GET / HTTP/1.1
host: good.internal
host: evil.attacker

RFC 9112 s3.2 требует, чтобы сервер отвечал 400 на любой HTTP/1.1-запрос, содержащий более одного заголовка Host. Бэкенды ведут себя по-разному:

nginx          — отклоняет с 400
Python stdlib  — принимает, возвращает ПЕРВЫЙ Host при поиске
Werkzeug       — принимает, возвращает ПЕРВЫЙ Host при поиске

Случай 2 — скрытая контрабанда (обход :authority)

Клиент отправляет:

:method: GET
:path: /
:scheme: https
:authority: good.internal
host: evil.attacker     <- индекс 0, первый Host (виден источнику)
host: good.internal     <- индекс 1, последний Host (используется валидатором h2)

h2 проверяет: последний Host (good.internal) == :authority (good.internal) — проходит.

Приложение получает оба заголовка Host. Понижение до HTTP/1.1 даёт:

GET / HTTP/1.1
host: evil.attacker
host: good.internal

Бэкенды, возвращающие первый Host при поиске по одному ключу, направляют запрос на evil.attacker, хотя h2 считал, что проверил good.internal. Полная рассинхронизация маршрутизации между тем, что проверил h2, и тем, что обрабатывает источник.


Контрольный случай — одиночный несовпадающий Host корректно отклоняется

:method: GET
:path: /
:scheme: https
:authority: good.internal
host: evil.attacker

h2 выбрасывает ProtocolError. Подтверждает, что пробел специфичен для дублирующихся заголовков Host.


Что может быть затронуто

Влияние зависит от архитектуры развёртывания:

  • Затронуто: обратные прокси или API-шлюзы, которые принимают HTTP/2 от клиентов и понижают до HTTP/1.1 при пересылке на бэкенды, где бэкенд использует первый заголовок Host для решений о маршрутизации
  • Затронутые бэкенды (проверено эмпирически): Python stdlib http.client, Werkzeug Headers — оба принимают дублирующийся Host и возвращают первое значение
  • Незатронутые бэкенды: nginx — отклоняет с 400 при дублирующемся Host
  • Незатронутые развёртывания: сквозной HTTP/2 без понижения до HTTP/1.1

Шаги для воспроизведения

Требования

pip install h2==4.4.0

Запуск

python3 poc_h2_duplicate_host.py

Ожидаемый вывод на уязвимой версии 4.4.0

СЛУЧАЙ 1 — Два заголовка Host, без :authority (оба пересылаются)
h2 пересылает заголовки Host: ['good.internal', 'evil.attacker']
Итоговый HTTP/1.1 запрос:
GET / HTTP/1.1
host: good.internal
host: evil.attacker

СЛУЧАЙ 2 — СКРЫТНЫЙ: :authority совпадает с ПОСЛЕДНИМ Host, первый Host протаскивается
:authority=['good.internal']  Host(s)=['evil.attacker', 'good.internal']
Проверка несоответствия h2 ПРОХОДИТ (:authority == последний Host).
Итоговый HTTP/1.1 запрос:
GET / HTTP/1.1
host: evil.attacker
host: good.internal

КОНТРОЛЬ — Один несовпадающий Host против :authority (корректно отклоняется)
[control] SENDER rejected: ProtocolError(...)

Проверка исправления на 4.4.1

pip install h2==4.4.1
python3 poc_h2_duplicate_host.py

Все три случая вызывают ProtocolError. Заголовки не пересылаются.


Исправление

Один счётчик, добавленный в существующий цикл в _validate_host_authority_header():

host_header_count = 0
for header in headers:
    if header[0] == b"host":
        host_header_count += 1
    yield header

if host_header_count > 1:
    raise ProtocolError("Request header block has multiple Host headers.")

Коммит: https://github.com/python-hyper/h2/commit/292a40829feefda98c8509dcdbbb4a57af9bd6a6


Благодарности

Обнаружено и сообщено Sunand Mohan (https://github.com/SunandM)

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