Уязвимости Request Smuggling в Pingora: как мы их обнаружили и устранили

В декабре 2025 года Cloudflare получила сообщения об уязвимостях перехвата запросов HTTP/1.x в фреймворке Pingora с открытым исходным кодом, когда Pingora используется для построения входящего прокси. Сегодня мы обсуждаем, как работают эти уязвимости и как мы исправили их в Pingora 0.8.0.

Уязвимости имеют идентификаторы CVE-2026-2833, CVE-2026-2835 и CVE-2026-2836. Эти проблемы были ответственно сообщены нам Раджатом Рагхавом (xclow3n) через нашу Программу вознаграждений за уязвимости.

CDN Cloudflare и трафик клиентов не были затронуты, как показало наше расследование. Клиентам Cloudflare не требуется никаких действий, и никакого влияния обнаружено не было. 

Из-за архитектуры сети Cloudflare эти уязвимости не могли быть использованы: Pingora не используется в качестве входящего прокси в CDN Cloudflare.

Однако эти проблемы затрагивают автономные развертывания Pingora, открытые для Интернета, и могут позволить злоумышленнику:

  • Обойти средства контроля безопасности на уровне прокси Pingora

  • Рассинхронизировать HTTP-запросы/ответы с бэкендами для проведения атак перехвата между пользователями (кража сессий или учетных данных)

  • Отравить кэши на уровне прокси Pingora, получающие контент от общих бэкендов

Мы выпустили Pingora 0.8.0 с исправлениями и усилением защиты. Хотя клиенты Cloudflare не пострадали, мы настоятельно рекомендуем пользователям фреймворка Pingora как можно скорее обновиться.

В чем заключалась уязвимость?

В сообщениях описывалось несколько различных полезных нагрузок атак HTTP/1, которые могли вызывать атаки на рассинхронизацию. Такие запросы могли привести к тому, что прокси и бэкенд по-разному определяли конец тела запроса, позволяя «протащить» второй запрос мимо проверок на уровне прокси. Исследователь предоставил доказательство концепции, демонстрирующее, как базовый обратный прокси Pingora неверно интерпретировал длину тела запроса и пересылал эти запросы на серверные бэкенды, такие как Node/Express или uvicorn.

Получив сообщения, наша инженерная команда немедленно начала расследование и подтвердила, что, как также подтвердил сообщающий, сам CDN Cloudflare не был уязвим. Однако команда также подтвердила, что уязвимости существуют, когда Pingora выступает в качестве входящего прокси для общих бэкендов.

По замыслу, фреймворк Pingora действительно допускает крайние случаи HTTP-запросов или ответов, которые не строго соответствуют RFC, потому что мы должны принимать такой трафик для клиентов с устаревшими HTTP-стеками. Но эта снисходительность имеет пределы, чтобы избежать уязвимостей для самой Cloudflare.

В данном случае Pingora имела не соответствующую RFC интерпретацию тел запросов в своем стеке HTTP/1, что позволило существовать этим атакам на рассинхронизацию. Развертывания Pingora внутри Cloudflare не напрямую подвержены входящему трафику, и мы обнаружили, что рабочий трафик, поступающий в сервисы Pingora, не подвергался этим ошибочным интерпретациям. Таким образом, атаки не могли быть использованы против самого трафика Cloudflare, в отличие от предыдущей уязвимости перехвата в Pingora, раскрытой в мае 2025 года.

Мы объясним, как работали эти полезные нагрузки атак, на каждом конкретном случае.

1. Преждевременное обновление без подтверждения 101

Первое сообщение показало, что запрос со значением заголовка Upgrade заставлял Pingora немедленно передавать последующие байты по HTTP-соединению, прежде чем бэкенд принял обновление (ответив 101 Switching Protocols). Таким образом, злоумышленник мог передать второй HTTP-запрос вслед за запросом на обновление по тому же соединению:

GET / HTTP/1.1
Host: example.com
Upgrade: foo


GET /admin HTTP/1.1
Host: example.com

Pingora разбирала только первоначальный запрос, а затем обрабатывала оставшиеся буферизированные байты как «обновленный» поток и передавала их напрямую бэкенду в режиме «сквозной передачи» из-за заголовка Upgrade (до тех пор, пока не был получен ответ).

Это совершенно не соответствует тому, как должен работать процесс обновления HTTP/1.1 согласно RFC 9110. Последующие байты должны интерпретироваться как часть обновленного потока только в случае получения заголовка 101 Switching Protocols; если же получен ответ 200 OK, последующие байты должны продолжать интерпретироваться как HTTP.

Fixing request smuggling vulnerabilities in Pingora OSS deployments

Злоумышленник, отправив запрос на обновление, а затем передав частичный HTTP-запрос, может вызвать атаку на рассинхронизацию. Pingora будет ошибочно интерпретировать оба как один и тот же обновленный запрос, даже если сервер бэкенда отклонит обновление с кодом 200.

Из-за некорректной сквозной передачи развертывание Pingora, получившее ответ, отличный от 101, все равно могло переслать второй частичный HTTP-запрос вышестоящему серверу как есть, обходя любую логику обработки ACL или WAF, определенную пользователем Pingora, и отравляя соединение с вышестоящим сервером так, что последующий запрос от другого пользователя мог неправильно получить ответ /admin.

Fixing request smuggling vulnerabilities in Pingora OSS deployments

После выполнения полезной нагрузки атаки Pingora и сервер бэкенда оказываются «рассинхронизированы». Сервер бэкенда будет ждать, пока, по его мнению, не завершится оставшаяся часть заголовка частичного запроса /attack, который переслала Pingora. Когда Pingora пересылает запрос другого пользователя, с точки зрения сервера бэкенда два заголовка объединяются, и злоумышленник теперь отравил ответ другого пользователя.

С тех пор мы исправили Pingora, чтобы переключать интерпретацию последующих байтов только после того, как вышестоящий сервер ответит 101 Switching Protocols.

Мы подтвердили, что Cloudflare не была затронута по двум причинам:

  1. Входящие прокси CDN не имеют такого некорректного поведения.

  2. Клиенты наших внутренних сервисов Pingora не пытаются конвейеризировать HTTP/1 запросы. Более того, сервис Pingora, с которым эти клиенты общаются напрямую, отключает keep-alive для этих запросов Upgrade, внедряя заголовк Connection: close; это предотвращает отправку дополнительных запросов, которые могли бы быть отправлены — и впоследствии перехвачены — по тому же соединению.

2. HTTP/1.0, закрывающее соединение ограничение и transfer-encoding

Сообщающий также продемонстрировал то, что казалось более классической атакой на рассинхронизацию типа «CL.TE», где прокси Pingora использовало бы Content-Length для фрейминга, в то время как бэкенд использовал бы Transfer-Encoding:

GET / HTTP/1.0
Host: example.com
Connection: keep-alive
Transfer-Encoding: identity, chunked
Content-Length: 29

0

GET /admin HTTP/1.1
X:

В примере сообщающего Pingora рассматривала бы все последующие байты после первого заголовка запроса GET / как часть тела этого запроса, но сервер бэкенда node.js интерпретировал бы тело как чанкированное и заканчивающееся на чанке нулевой длины. На самом деле здесь происходит несколько вещей:

  1. Распознавание чанкированного кодирования в Pingora было довольно упрощенным (только проверялось, является ли Transfer-Encoding «chunked») и предполагало, что может быть только одно кодирование или заголовок Transfer-Encoding. Но RFC требует, чтобы последним кодированием был chunked для применения чанкированного фрейминга. Согласно RFC, этот запрос должен иметь чанкированное тело сообщения (если бы это не был HTTP/1.0 — об этом ниже).

  2. Pingora также фактически не использовала Content-Length (потому что Transfer-Encoding переопределяет Content-Length согласно RFC). Из-за нераспознанного Transfer-Encoding и версии HTTP/1.0 тело запроса вместо этого рассматривалось как ограниченное закрытием соединения (что означает, что конец тела ответа отмечается закрытием базового транспортного соединения). Отсутствие заголовков фрейминга также вызывало такую же ошибочную интерпретацию в HTTP/1.0. Хотя тела ответов могут быть ограничены закрытием соединения, тела запросов никогда не ограничиваются закрытием соединения. Фактически, это уточнение теперь явно выделено в отдельном примечании в RFC 9112.

  3. Это запрос HTTP/1.0, который не определял Transfer-Encoding. RFC требует, чтобы запросы HTTP/1.0, содержащие Transfer-Encoding, «рассматривали сообщение как имеющее ошибочное фреймирование» и закрывали соединение. Парсеры, такие как в nginx и hyper, просто отвергают эти запросы, чтобы избежать неоднозначного фреймирования.

Fixing request smuggling vulnerabilities in Pingora OSS deployments

Когда злоумышленник помещает частичный заголовок HTTP-запроса в конвейер после запроса HTTP/1.0 + Transfer-Encoding, Pingora ошибочно интерпретировала этот частичный заголовок как часть того же запроса, а не как отдельный запрос. Это позволяет провести ту же атаку на рассинхронизацию (desync), что описана в примере с преждевременным Upgrade.

Это указывало на более фундаментальное неверное прочтение RFC, особенно в части фреймирования ответов и запросов. С тех пор мы исправили некорректный парсинг множественного Transfer-Encoding, строго следуем правилам определения длины запроса, чтобы тела HTTP-запросов никогда не считались ограниченными закрытием соединения, и отвергаем сообщения запросов с некорректным Content-Length и HTTP/1.0 + Transfer-Encoding. К дополнительным мерам защиты, которые мы добавили, относится отклонение запросов CONNECT по умолчанию, потому что логика HTTP-прокси в настоящее время не рассматривает CONNECT как особый для целей проксирования с обновлением до CONNECT, и у этих запросов особые правила фреймирования сообщений. (Примечание: входящие запросы CONNECT отклоняются CDN Cloudflare.)

Когда мы исследовали и проанализировали наши внутренние сервисы, мы не обнаружили запросов, поступающих в наши сервисы Pingora, которые были бы неверно интерпретированы. Мы выяснили, что нижестоящие прокси-уровни в CDN пересылают запросы только как HTTP/1.1, отвергают неоднозначное фреймирование (например, некорректный Content-Length) и пересылают только один заголовок Transfer-Encoding: chunked для запросов с чанкированием.

3. Формирование ключа кеша

Исследователь также сообщил об одной другой уязвимости отравления кеша, касающейся формирования ключа CacheKey по умолчанию. Наивная реализация по умолчанию учитывала только путь URI (без других факторов, таких как заголовок Host или схема HTTP вышестоящего сервера), что означало, что разные хосты, использующие один и тот же HTTP-путь, могли конфликтовать и отравлять кеш друг друга.

Это затрагивало бы пользователей альфа-функции прокси-кеширования, которые выбрали реализацию CacheKey по умолчанию. С тех пор мы удалили это значение по умолчанию, потому что, хотя использование чего-то вроде схема_HTTP + хост + URI имеет смысл для многих приложений, мы хотим, чтобы пользователи внимательно подходили к самостоятельному формированию своих ключей кеша. Например, если их прокси-логика будет условно изменять URI или метод вышестоящего запроса, эта логика, вероятно, также должна учитываться в схеме ключа кеша, чтобы избежать отравления.

Внутри Cloudflare ключ кеша по умолчанию использует ряд факторов для предотвращения отравления ключа кеша и никогда не использовал ранее предоставленное значение по умолчанию.

Рекомендация

Если вы используете Pingora в качестве прокси, обновитесь до Pingora 0.8.0 при первой возможности.

Мы приносим извинения за влияние, которое эта уязвимость могла оказать на пользователей Pingora. Поскольку Pingora занимает свое место как критически важная интернет-инфраструктура за пределами Cloudflare, мы считаем важным, чтобы фреймворк по умолчанию способствовал использованию строгого соответствия RFC, и будем продолжать эту работу. Очень немногим пользователям фреймворка придется иметь дело с тем же «диким Интернетом», что и Cloudflare. Мы намерены обеспечить более строгое соблюдение последних стандартов RFC по умолчанию, что повысит безопасность для пользователей Pingora и продвинет Интернет в целом к лучшим практикам.

Хронология раскрытия и реагирования

- 2025‑12‑02: Сообщение об уязвимости, основанной на Upgrade для смиглинга, через программу Bug Bounty.

- 2026‑01‑13: Сообщение о проблемах с парсингом Transfer‑Encoding / HTTP/1.0.

- 2026-01-18: Сообщение о проблеме формирования ключа кеша по умолчанию.

- 2026‑01‑29 по 2026‑02‑13: Исправления проверены с автором отчета. Продолжается работа над дополнительными проверками соответствия RFC.

- 2026-02-25: Удаление ключа кеша по умолчанию и дополнительные проверки RFC подтверждены исследователем.

- 2026‑03-02: Выпущена Pingora 0.8.0.

- 2026-03-04: Опубликованы консультационные сообщения CVE.

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

Мы благодарим Раджата Рагава (xclow3n) за отчет, подробные воспроизведения и проверку исправлений через нашу программу Bug Bounty. Для получения дополнительной информации, пожалуйста, ознакомьтесь с их соответствующим блогом.