kai.kzLab

Сбой Cloudflare 2 июля 2019 года: как одно регулярное выражение в WAF загрузило процессоры по всему миру

Команда KAI Lab · · 9 мин чтения

2 июля 2019 года в 13:42 UTC Cloudflare выкатила новое правило WAF, и процессоры, которые обслуживают HTTP и HTTPS-трафик, по всей сети компании загрузились до 100%; 27 минут сайты клиентов отдавали ошибку 502. Причина — регулярное выражение с катастрофическим откатом (backtracking): на некоторых строках движок перебирал огромное число вариантов совпадения.

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

Главное о сбое в одной таблице

ВопросОтвет
Когда2 июля 2019, 13:42–14:09 UTC, 27 минут
Что выкатилиновое правило в WAF Managed Rules против XSS-атак, в режиме simulate — без блокировки
Что сломалосьфрагмент регулярки .*(?:.*=.*) вызвал катастрофический backtracking
Что видели пользователиошибки 502 Bad Gateway; в худший момент трафик упал на 82%
Атака?нет, Cloudflare это прямо исключила
Как остановилимеханизмом global terminate выключили WAF Managed Rules во всём мире
Почему сразу вездеправила WAF доставлялись через хранилище Quicksilver за секунды, без поэтапной выкатки
Что измениливернули защиту от перерасхода CPU, проверили все 3 868 правил, запланировали движок re2 или Rust regex и поэтапную выкатку правил

Хронология сбоя Cloudflare

Время (UTC)Что произошло
Несколько недель до сбояПри рефакторинге WAF, который должен был снизить расход CPU, по ошибке удаляют защиту от регулярок, съедающих процессор
2 июля, 13:31Инженер вливает pull request с новым правилом
13:37TeamCity собирает правила, тесты проходят
13:42Автоматическая выкатка: правило уходит на все серверы
13:45Первый вызов в PagerDuty: упал синтетический тест WAF. Следом — падения других сквозных тестов, алерт о глобальном падении трафика, массовые 502
14:00Виновник — WAF, версию атаки отбрасывают
14:02Предлагают выключить WAF целиком механизмом global terminate
14:07WAF выключен по всему миру
14:09Трафик и загрузка CPU вернулись к норме
14:52Причина понятна, исправление готово, WAF снова включён глобально
12 июляТехнический директор Джон Грэм-Камминг публикует подробный разбор

Почему регулярка съела процессор

Правило должно было лучше ловить inline JavaScript в XSS-атаках. В середине длинного выражения стоял фрагмент .*(?:.*=.*): несколько «жадных» .* подряд и знак равенства между ними.

Движки с откатом, как PCRE, работают перебором. Первый .* забирает всю строку, потом отдаёт по одному символу, чтобы дать шанс остальной части выражения. Когда таких .* несколько, движок перебирает все способы поделить строку между ними. Cloudflare посчитала шаги: для строки x=x их 23, для x=xx — 33, а с двадцатью x после знака равенства — 555. Рост сверхлинейный, и чем длиннее строка, тем хуже.

Эффект легко увидеть у себя. Модуль re в Python тоже работает с откатом:

$ for n in 1000 2000 4000; do
>   python3 -c "import re, time; s='x'*$n; t=time.perf_counter(); re.search(r'.*(?:.*=.*)', s); print($n, round(time.perf_counter()-t, 3), 's')"
> done
1000 0.161 s
2000 1.231 s
4000 9.847 s

В строке нет знака =, совпадения нет, и движок пробует все варианты. Строка вдвое длиннее — в восемь раз дольше. Сервер, который прогоняет через правило каждый HTTP-запрос, в таком режиме занят одним перебором.

Движки re2 и Rust regex устроены иначе и дают гарантии по времени выполнения. Плата за это — в RE2 нет обратных ссылок и lookaround: для них известны только решения с откатом. Пакет regexp в Go принимает синтаксис RE2 и гарантирует время, линейное по длине входа; этот же синтаксис использует, например, Prometheus.

Почему упало сразу везде

Обычный софт Cloudflare выходит поэтапно. Сначала точка присутствия DOG, через которую ходят только сотрудники, потом PIG с частью трафика бесплатных клиентов, потом три канареечные точки в разных частях света и только потом весь мир. Это занимает часы или дни.

Правила WAF шли другим путём — через Quicksilver, распределённое хранилище ключей и значений. Изменение доходит через него до всех машин в мире за секунды: p99 в среднем 2,29 секунды. Скорость нужна, чтобы закрывать новые уязвимости раньше, чем их начнут массово использовать. Но процедура разрешала так же, в один шаг, выкатывать и неэкстренные правила. Новое XSS-правило было именно таким.

Сработало и совпадение. Защиту, которая ограничила бы расход CPU одной регуляркой, удалили за несколько недель до сбоя, при рефакторинге WAF, который как раз должен был снизить нагрузку на процессор. А тесты проверяли, что правило срабатывает где надо, но не умели замечать, сколько процессора оно ест.

Почему чинили 27 минут, а не пять

Алерты пришли через три минуты после выкатки. Остальное время ушло на то, что мешает в любом крупном инциденте.

  • Первая гипотеза — атака. Сначала подозревали атаку невиданного типа, и эту версию отбросили только в 14:00.
  • Свои инструменты недоступны. Внутренние панели были закрыты сервисом Cloudflare Access, который лёг вместе со всеми. Пришлось использовать обходной путь, который редко тренировали. У части SRE учётные данные отключились по таймауту безопасности, потому что панелью давно не пользовались.
  • Откат был медленным. План отката требовал дважды прогнать полную сборку WAF. Поэтому выбрали global terminate — заранее встроенный механизм, который выключает один компонент во всём мире.
  • Клиенты не могли ничего сделать сами. Дашборд и API Cloudflare тоже ходят через её сеть и были недоступны. Страницу статуса обновляли недостаточно быстро.

Cloudflare сама перечислила 11 пунктов, что пошло не так. Инженер, написавший регулярку, — только первый из них. Остальные десять — о том, почему система позволила одной ошибке стать глобальной и почему её так долго выключали.

Уроки для DevOps

1. Правила и конфигурация — тот же релиз

Правило WAF — «всего лишь конфиг», но положило сеть. Через пять лет по тому же сценарию сбой CrowdStrike уронил миллионы компьютеров: контент-обновление ушло на всех сразу. После сбоя Cloudflare перевела правила на поэтапную выкатку, как у остального софта, и оставила глобальную выкатку только для активных атак.

2. У регулярки есть цена в процессоре

Регулярные выражения живут везде: location ~ в nginx (для них нужна библиотека PCRE), парсинг логов, правила WAF, валидация ввода в приложениях. Проверяйте их не только на правильность, но и на худший случай: длинная строка без совпадения. Где можно, берите движок с линейной гарантией, а на входные данные ставьте лимит длины.

3. Защитные механизмы нужно тестировать, как функциональность

Ограничитель CPU исчез при рефакторинге, и ни один тест этого не заметил. Если у системы есть предохранитель — таймаут, лимит, circuit breaker, — на него нужен отдельный тест, который падает, когда предохранитель пропал.

4. Рубильник строят заранее

Выключить WAF за минуты удалось только потому, что global terminate уже был встроен в систему. У каждого рискованного компонента должен быть способ быстро выключить его или обойти — feature-флаг, байпас в балансировщике, отключение модуля. Так же быстро нужен стоп-кран и в деплое: у Knight Capital в 2012 году его не было, и сбой длился 45 минут.

5. Аварийный доступ не должен зависеть от того, что упало

Внутренние инструменты Cloudflare были закрыты её же продуктом. У AWS в 2017 году консоль панели статуса зависела от упавшего S3. Проверьте, как вы попадёте в консоль, мониторинг и чат дежурных, если лёг ваш SSO, VPN или облако. Аварийные учётки проверяют на учениях, а не в первый раз во время сбоя.

Что проверить у себя завтра:

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

Реши сам

Вопрос 1. Правило работало в режиме simulate и ничего не блокировало. Почему оно всё равно уронило сеть?

  1. В режиме simulate правило всё равно блокировало запросы
  2. Simulate отменяет действие правила, но не проверку: регулярка выполнялась на каждом запросе и съедала CPU
  3. Режим simulate отключает кэш
  4. Правило случайно выкатили в режиме блокировки
Показать ответ

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

Вопрос 2. Почему команда не откатила одно правило, а выключила WAF во всём мире?

  1. Не нашли, какое правило виновато
  2. Откат по плану требовал дважды прогнать полную сборку WAF, а global terminate выключал компонент за минуты
  3. Так требовали клиенты
  4. WAF нельзя откатывать по правилам
Показать ответ

Верный ответ — 2. Медленный откат хуже быстрого рубильника. Выключенный WAF временно ослабил защиту, но вернул трафик. Когда исправление было готово, в 14:52 WAF включили обратно.

Частые вопросы

Был ли сбой Cloudflare 2 июля 2019 года атакой? Нет. В первом же сообщении в день сбоя Cloudflare написала, что это не атака. Причина — новое правило в собственном WAF компании.

Сколько длился сбой? 27 минут: выкатка началась в 13:42 UTC, в 14:07 WAF выключили, к 14:09 трафик и загрузка CPU вернулись к норме. Сам WAF снова включили в 14:52.

Что такое катастрофический backtracking? Поведение движков регулярных выражений с откатом, когда число шагов на некоторых строках растёт сверхлинейно. Обычно виноваты несколько .* или вложенные повторения подряд. На длинной строке без совпадения такая регулярка может выполняться секунды и минуты.

Что Cloudflare изменила после сбоя? Вернула защиту от перерасхода CPU и вручную проверила все 3 868 правил WAF. Запланировала профилирование производительности правил в тестах, переход на движок re2 или Rust regex и поэтапную выкатку правил. Дашборд и API получили аварийный путь в обход её сети, обновление статус-страницы автоматизировали.

Пройти похожий сбой самому

В симуляторе DevOps KAI Lab разбор Cloudflare стоит в восьмом месяце, посвящённом мониторингу. Там же — дежурство «Ночь Чёрной пятницы», где алерты сыплются один за другим и надо отделить главное от шума, пока падает оплата.

Проверить, как вы действуете в похожих ситуациях, можно бесплатно: тест навыков DevOps — 18 ситуаций, 10–15 минут, без регистрации.

Источники

Читать дальше