Уязвимости QUIC: как мы остановили DDoS-атаки через подтверждения пакетов

10 апреля 2025 года в 12:10 по UTC исследователь безопасности уведомил Cloudflare о двух уязвимостях (CVE-2025-4820 и CVE-2025-4821), связанных с обработкой подтверждений (ACK) пакетов QUIC, через нашу программу Public Bug Bounty. Это были уязвимости типа DDoS в библиотеке quiche и сервисах Cloudflare, которые её используют. quiche — это реализация протокола QUIC с открытым исходным кодом от Cloudflare, который является транспортным протоколом, лежащим в основе HTTP/3.

После уведомления инженеры Cloudflare исправили затронутую инфраструктуру, и исследователь подтвердил, что вектор DDoS-атаки был устранён. Расследование Cloudflare не выявило никаких свидетельств того, что уязвимости эксплуатировались или что какие-либо клиенты пострадали. Затронутыми оказались версии quiche до 0.24.4.

Здесь мы объясним, почему ACK важны для проектирования интернет-протоколов и как они помогают обеспечивать справедливое использование сети. Наконец, мы объясним уязвимости и обсудим наше решение для атаки Optimistic ACK: динамическую частоту пропуска, учитывающую CWND и масштабирующуюся в зависимости от скорости отправки соединения.

Интернет-протоколы и векторы атак

QUIC — это интернет-транспортный протокол, который предлагает функциональность, эквивалентную TCP (Transmission Control Protocol) и TLS (Transport Layer Security). QUIC работает поверх UDP (User Datagram Protocol), по умолчанию зашифрован и предлагает несколько преимуществ по сравнению с предыдущим набором протоколов (включая меньшее время handshake, миграцию соединений и предотвращение head-of-line blocking, которое может проявляться в TCP). Как и TCP, QUIC полагается на подтверждения пакетов (ACK) для общего прогресса. Например, ACK используются для проверки активности, валидации, сигналов восстановления потерь и сигналов алгоритма управления перегрузкой.

ACK являются важным источником сигналов для интернет-протоколов, что требует валидации, чтобы убедиться, что вредоносный узел не подрывает эти сигналы. В реализации QUIC от Cloudflare, quiche, отсутствовала проверка диапазона ACK, что означало, что узел мог отправить диапазон ACK для пакетов, никогда не отправленных конечной точкой; это было исправлено в CVE-2025-4821. Кроме того, сложный злоумышленник мог провести атаку, предсказывая и упреждающе отправляя ACK (методика, называемая Optimistic ACK); это было исправлено в CVE-2025-4820. Используя отсутствие проверки ACK, злоумышленник может заставить конечную точку искусственно увеличить свою скорость отправки, тем самым получив несправедливое преимущество перед другими соединениями. В крайнем случае это может стать вектором DDoS-атаки из-за более высокой загрузки ЦП сервера и усиления сетевого трафика.

Справедливость и управление перегрузками

Типичная настройка CDN включает сотни серверных процессов, обслуживающих тысячи одновременных соединений. Каждое соединение имеет собственный алгоритм восстановления и управления перегрузкой, который отвечает за определение его справедливой доли в сети. Интернет — это общий ресурс, который зависит от корректно работающих транспортных протоколов, правильно реализующих управление перегрузкой для обеспечения справедливости.

Чтобы проиллюстрировать это, рассмотрим общую сеть, где первое соединение (синее) работает на полную мощность. Когда новое соединение (зелёное) присоединяется и исследует доступную пропускную способность, это вызовет потерю пакетов, тем самым сигнализируя синему соединению уменьшить скорость отправки. Исследование может быть очень динамичным, и хотя сходимость может занять время, предполагается, что оба соединения в итоге разделят равную пропускную способность в сети.

Defending QUIC from acknowledgement-based DDoS attacks

Новое соединение присоединяется к общей сети. Существующие потоки освобождают место для нового потока.

Для обеспечения справедливости и производительности каждая конечная точка использует алгоритм Управления Перегрузкой. Существуют различные алгоритмы, но для наших целей рассмотрим Cubic, алгоритм, основанный на потерях. Cubic в установившемся состоянии периодически исследует более высокие скорости отправки. По мере того как узел подтверждает (ACK) новые пакеты, Cubic разблокирует дополнительную пропускную способность отправки (окно перегрузки), чтобы исследовать ещё более высокие скорости отправки. Cubic продолжает увеличивать скорость отправки до тех пор, пока не обнаружит сигналы перегрузки (например, потерю пакетов), указывающие на то, что сеть, возможно, работает на пределе, и соединение должно снизить скорость отправки.

Defending QUIC from acknowledgement-based DDoS attacks

Алгоритм управления перегрузкой Cubic реагирует на потери в сети.

Роль ACK

ACK — это механизм обратной связи, который интернет-протоколы используют для прогресса. Сервер, обслуживающий большую загрузку файла, отправляет эти данные клиенту в нескольких пакетах. Поскольку сети подвержены потерям, клиент отвечает за отправку ACK, когда он получил пакет от сервера, тем самым подтверждая доставку и прогресс. Отсутствие ACK указывает на то, что пакет был потерян и данные, возможно, требуют повторной передачи. Эта обратная связь позволяет серверу подтвердить, что клиент получил все запрошенные данные.

Defending QUIC from acknowledgement-based DDoS attacks

Сервер доставляет пакеты, а клиент отвечает ACK.

Defending QUIC from acknowledgement-based DDoS attacks

Сервер доставляет пакеты, но пакет [2] потерян. Клиент отвечает ACK только для пакетов [1, 3], тем самым сигнализируя, что пакет [2] был потерян.

В QUIC номера пакетов не обязательно должны быть последовательными; это означает, что пропуск номеров пакетов изначально поддерживается. Кроме того, Фрейм ACK в QUIC может содержать пропуски и несколько диапазонов ACK. Как мы увидим, встроенная поддержка пропуска номеров пакетов — это уникальная особенность QUIC (по сравнению с TCP), которая поможет нам обеспечить проверку ACK.

Defending QUIC from acknowledgement-based DDoS attacks

Сервер доставляет пакеты, но пропускает пакет [4]. Клиент отвечает ACK только для полученных пакетов и не отправляет ACK для пакета [4].

ACK также предоставляют сигналы, которые контролируют скорость отправки конечной точки и помогают обеспечить справедливость и производительность. Задержка между ACK, вариации задержки и отсутствие ACK предоставляют ценные сигналы, которые указывают на изменение в сети и являются важными входными данными для алгоритма управления перегрузкой.

Пропуск пакетов для избежания задержки ACK

QUIC позволяет конечным точкам кодировать задержку ACK: время, на которое подтверждение для пакета с номером 'X' было намеренно задержано с момента получения конечной точкой пакета с номером 'X'. Эта задержка может быть результатом обычной обработки пакетов или оптимизацией, специфичной для реализации. Например, поскольку обработка ACK может быть дорогостоящей (как для ЦП, так и для сети), задержка ACK позволяет выполнять пакетную обработку и снижать связанные с этим накладные расходы.

Если отправитель хочет получить более быстрое подтверждение при PTO, он может пропустить номер пакета, чтобы устранить задержку подтверждения. -- https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.4

Однако, поскольку задержка сигнала ACK также задерживает обратную связь от узла, это может быть вредным для восстановления после потерь. Поэтому конечные точки QUIC могут сигнализировать узлу о необходимости избегать задержки пакета ACK путём пропуска номера пакета. Эта деталь станет важной, как мы увидим далее в посте.

Проверка диапазона ACK

Ожидается, что корректно работающий клиент должен отправлять ACK только для пакетов, которые он получил. Отсутствие проверки означало, что клиент мог отправить очень большой диапазон ACK для пакетов, никогда не отправленных сервером. Например, предполагая, что сервер отправил пакеты 0-5, клиент мог отправить Фрейм ACK с диапазоном 0-100.

Само по себе это не является большой проблемой, поскольку quiche достаточно умна, чтобы отбрасывать большие ACK и обрабатывать только ACK для отправленных ею пакетов. Однако, как мы увидим в следующем разделе, это облегчило эксплуатацию уязвимости Optimistic ACK.

Исправление заключалось в применении проверки диапазона ACK на основе наибольших пакетов, отправленных сервером, и разрыве соединения при нарушении. Это соответствует рекомендации RFC.

Конечная точка ДОЛЖНА рассматривать получение подтверждения для пакета, который она не отправляла, как ошибку соединения типа PROTOCOL_VIOLATION, если она способна обнаружить это условие. -- https://www.rfc-editor.org/rfc/rfc9000#section-13.1

Defending QUIC from acknowledgement-based DDoS attacks

Сервер проверяет ACK: клиент отправляет ACK для пакетов [4..5], не отправленных сервером. Сервер закрывает соединение, так как проверка ACK не пройдена.

Атака Optimistic ACK (оптимистичным подтверждением)

В следующем сценарии предположим, что клиент пытается осуществить атаку Optimistic ACK на сервер. Цель клиента, осуществляющего атаку, — заставить сервер отправлять данные с высокой скоростью. Чтобы достичь высокой скорости отправки, клиенту необходимо быстро доставлять ACK обратно на сервер, тем самым создавая искусственно низкий сигнал RTT / высокой пропускной способности. Поскольку номера пакетов обычно монотонно увеличиваются, умный клиент может предсказать следующий номер пакета и упреждающе отправлять ACK (искусственный ACK).

Defending QUIC from acknowledgement-based DDoS attacks

Атака Optimistic ACK: клиент предсказывает пакеты, отправленные сервером, и упреждающе отправляет ACK. Проверка ACK здесь не помогает.

Если сервер имеет надлежащую проверку ACK, недопустимый ACK для пакетов, еще не отправленных сервером, должен привести к закрытию соединения (без проверки диапазона ACK атаку тривиально выполнить). Следовательно, злонамеренному клиенту нужно быть умным в распределении времени отправки искусственных ACK, чтобы они прибывали как раз в момент, когда сервер отправил пакет. Если атака выполнена правильно, сервер увидит очень низкий RTT, что приведет к завышенной скорости отправки.

Конечная точка, которая подтверждает пакеты, которые она не получала, может привести к тому, что контроллер перегрузки разрешит отправку со скоростями, превышающими возможности сети. Конечная точка МОЖЕТ пропускать номера пакетов при отправке, чтобы обнаружить такое поведение. Затем конечная точка может немедленно закрыть соединение с ошибкой соединения типа PROTOCOL_VIOLATION -- https://www.rfc-editor.org/rfc/rfc9000#section-21.4

Defending QUIC from acknowledgement-based DDoS attacks

Предотвращение атаки Optimistic ACK: клиент предсказывает пакеты, отправленные сервером, и упреждающе отправляет ACK. Поскольку сервер пропустил пакет [4], он может обнаружить недопустимый ACK и закрыть соединение.

QUIC RFC упоминает атаку Optimistic ACK и предлагает пропускать пакеты для обнаружения этой атаки. Пропуская пакеты, клиент не может легко предсказать следующий номер пакета и рискует разрывом соединения, если сервер реализует проверку недопустимого диапазона ACK. Однако детали реализации – например, сколько номеров пакетов пропускать и как часто – отсутствуют.

Характер передачи [злонамеренного] клиента не указывает на какое-либо вредоносное поведение.

Таким образом, битрейт в направлении сервера соответствует нормальному поведению. Учитывая, что пакеты QUIC шифруются сквозным шифрованием, промежуточное устройство не может идентифицировать атаку, анализируя трафик клиента. -- MAY недостаточно! QUIC-серверы ДОЛЖНЫ пропускать номера пакетов

В идеале клиент хотел бы использовать как можно меньше ресурсов, одновременно заставляя сервер использовать как можно больше. Фактически, как подтвердили исследователи безопасности в своей статье: сложно обнаружить вредоносный QUIC-клиент с помощью внешнего анализа трафика, и поэтому QUIC-реализациям необходимо устранять атаку Optimistic ACK путем пропуска пакетов.

Уязвимость Optimistic ACK не уникальна для QUIC. Фактически уязвимость была впервые обнаружена в TCP. Однако, поскольку TCP изначально не поддерживает пропуск номеров пакетов, атаку Optimistic ACK в TCP сложнее устранить, и она может потребовать дополнительного анализа DDoS. Благодаря возможности пропуска пакетов, QUIC способен предотвращать этот тип атаки на уровне протокола и более эффективно обеспечивать корректность и справедливость в ненадежных сетях.

Как часто пропускать номера пакетов

Согласно QUIC RFC, пропуск номеров пакетов в настоящее время преследует две цели. Первая — получить более быстрое подтверждение для восстановления после потерь, а вторая — устранить атаку Optimistic ACK. Таким образом, QUIC-реализации, пропускающей пакеты для защиты от атаки Optimistic ACK, необходимо пропускать достаточно часто, чтобы устранить атаку, учитывая при этом влияние на устранение задержки ACK.

Поскольку пропуск пакетов должен быть непредсказуемым, простая реализация может заключаться в пропуске номеров пакетов на основе случайного числа из статического диапазона. Однако, поскольку количество пакетов увеличивается с ростом скорости отправки, это имеет недостаток – отсутствие адаптации к скорости отправки. При меньших скоростях отправки статический диапазон будет слишком частым, а при более высоких скоростях он будет недостаточно частым и, следовательно, менее эффективным. Также, arguably, наиболее важно проверять скорость отправки именно при более высоких скоростях. Поэтому кажется необходимым адаптировать частоту пропуска на основе скорости отправки.

Окно перегрузки (CWND) — это параметр, используемый алгоритмами управления перегрузкой для определения объема байтов, которые можно отправить за раунд. Поскольку скорость отправки увеличивается на основе объема байтов, для которых получен ACK (ограниченного отправленными байтами), мы утверждаем, что CWND является отличным прокси для динамической корректировки частоты пропуска. Эта частота пропуска с учетом CWND позволяет всем соединениям, независимо от текущей скорости отправки, эффективно устранять атаку Optimistic ACK.

// c: текущий номер пакета
// s: диапазон случайных номеров пакетов для пропуска
//
// curr_pn
//  |
//  v                 |--- (upper - lower) ---|
// [c x x x x x x x x s s s s s s s s s s s s s x x]
//    |--min_skip---| |------skip_range-------|

const DEFAULT_INITIAL_CONGESTION_WINDOW_PACKETS: usize = 10;
const MIN_SKIP_COUNTER_VALUE: u64 = DEFAULT_INITIAL_CONGESTION_WINDOW_PACKETS * 2;

let packets_per_cwnd = (cwnd / max_datagram_size) as u64;
let lower = packets_per_cwnd / 2;
let upper = packets_per_cwnd * 2;

let skip_range = upper - lower;
let rand_skip_value = rand(skip_range);

let skip_pn = MIN_SKIP_COUNTER_VALUE + lower + rand_skip_value;

Расчет частоты пропуска в quiche.

Хронология

Все временные метки указаны по UTC.

  • 2025–04-10 12:10 - Cloudflare уведомлена об уязвимостях проверки ACK и Optimistic ACK через программу Bug Bounty.

  • 2025-04-19 00:20 – Cloudflare подтверждает, что обе уязвимости воспроизводимы, и начинает работу над исправлением.

  • 2025-05-02 20:12 - Исправление безопасности завершено, начинается обновление инфраструктуры.

  • 2025–05-16 04:52 - Обновление инфраструктуры Cloudflare завершено.

  • Выпущена новая версия quiche.

Заключение

Мы хотели бы искренне поблагодарить Луи Наварра и Оливье Бонавантюра из UCLouvain, которые ответственно раскрыли эту проблему через нашу Программу вознаграждений за ошибки Cloudflare, что позволило нам выявить и устранить уязвимость. Они также опубликовали статью со своими выводами, уведомив 10 других реализаций QUIC, которые также страдали от уязвимости Optimistic ACK.