Сбой 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:37 | TeamCity собирает правила, тесты проходят |
| 13:42 | Автоматическая выкатка: правило уходит на все серверы |
| 13:45 | Первый вызов в PagerDuty: упал синтетический тест WAF. Следом — падения других сквозных тестов, алерт о глобальном падении трафика, массовые 502 |
| 14:00 | Виновник — WAF, версию атаки отбрасывают |
| 14:02 | Предлагают выключить WAF целиком механизмом global terminate |
| 14:07 | WAF выключен по всему миру |
| 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 и ничего не блокировало. Почему оно всё равно уронило сеть?
- В режиме simulate правило всё равно блокировало запросы
- Simulate отменяет действие правила, но не проверку: регулярка выполнялась на каждом запросе и съедала CPU
- Режим simulate отключает кэш
- Правило случайно выкатили в режиме блокировки
Показать ответ
Верный ответ — 2. Тестовый режим защищает от ложных блокировок, но не от стоимости вычислений. Чтобы понять, сработало бы правило, его надо выполнить. Поэтому новые правила проверяют и на производительность, а не только на результат.
Вопрос 2. Почему команда не откатила одно правило, а выключила WAF во всём мире?
- Не нашли, какое правило виновато
- Откат по плану требовал дважды прогнать полную сборку WAF, а global terminate выключал компонент за минуты
- Так требовали клиенты
- 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 минут, без регистрации.
Источники
- Cloudflare, Джон Грэм-Камминг. Details of the Cloudflare outage on July 2, 2019, 12.07.2019 — хронология, регулярное выражение, 11 причин и план исправлений.
- Cloudflare. Cloudflare outage caused by bad software deploy (updated), 02.07.2019 — первое сообщение: не атака, падение трафика на 82%, CPU до 100%.
- Go. Документация пакета regexp — гарантия линейного времени и синтаксис RE2.
- Google. RE2 — движок регулярных выражений без отката, ограничения синтаксиса.
- nginx. Сборка из исходных файлов — PCRE нужна для регулярных выражений в
location. - Prometheus. Querying basics — регулярные выражения в синтаксисе RE2.