[Перевод] Технические детали недавнего сбоя расширений Firefox
Об авторе. Эрик Рескорла — технический директор группы Firefox в Mozilla
Недавно в Firefox произошёл инцидент, когда большинство дополнений (расширений, аддонов) перестали работать. Это связано с ошибкой с нашей стороны: мы не заметили, что истёк срок действия одного из сертификатов, который используется для подписи дополнений, что привело к отключению подавляющего большинства из них. Теперь, когда мы исправили проблему, и большинство дополнений восстановлены, я хотел бы подробно рассказать, что произошло, почему и как мы всё починили.
Для справки: расширения и их подпись
Хотя многие используют Firefox как есть из коробки, браузер также поддерживает мощный механизм расширений. Они добавляют в Firefox сторонние функции, расширяющие возможности, которые мы предлагаем по умолчанию. В настоящее время существует более 15 000 дополнений Firefox: от блокировки рекламы до управления сотнями вкладок.
Firefox требует, чтобы все установленные дополнения были подписаны цифровой подписью. Это требование предназначено для защиты пользователей от вредоносных расширений, требуя минимального стандарта проверки сотрудниками Mozilla. До того, как мы ввели это требование в 2015 году, у нас были серьёзные проблемы с вредоносными расширениями.
Подпись работает через предустановленный «корневой сертификат» Firefox. Он хранится в автономном режиме в аппаратном модуле безопасности (HSM). Каждые несколько лет он используется для подписания нового «промежуточного сертификата», который хранится в онлайне и используется в процессе подписания. Когда расширение представлено для подписи, мы генерируем новый временный «сертификат конечного объекта» (end-entity certificate) и подписываем его с помощью промежуточного сертификата. Затем сертификат конечного объекта используется для подписи расширения. Визуально это выглядит следующим образом:
Обратите внимание, что у каждого сертификата есть «субъект» (которому принадлежит сертификат) и «эмитент» (подписавший). В случае корневого сертификата это одно и то же, но для других сертификатов эмитентом является субъект, который его подписал.
Важным моментом здесь является то, что каждое дополнение подписывается собственным сертификатом конечного объекта, но почти у всех аддонов один и тот же промежуточный сертификат (несколько очень старых дополнений были подписаны другим промежуточным звеном). Именно здесь возникла проблема: у каждого сертификата есть фиксированный срок действия. До или после этого окна сертификат не будет принят, и расширение, подписанное этим сертификатом, не может быть загружено в Firefox. К сожалению, промежуточный сертификат, который мы использовали, истёк 4 мая после 1:00 UTC, и сразу же каждое дополнение, которое подписано этим сертификатом, стало непроверяемым и не могло быть загружено в Firefox.
Хотя срок действия всех дополнений истёк около часа ночи, последствия ощутились не сразу. Причина в том, что Firefox не постоянно проверяет дополнения на валидность. Они проверяются примерно каждые 24 часа, причём время проверки отличается для каждого пользователя. В результате, некоторые люди испытали проблемы сразу, некоторые — гораздо позже. Мы в Mozilla впервые узнали о проблеме около 18:00 PST в пятницу 3 мая и сразу собрали команду, чтобы исправить ситуацию.
Ограничение ущерба
Как только мы поняли, с чем столкнулись, то предприняли несколько шагов, чтобы избежать ухудшения ситуации.
Во-первых, мы отключили подписание новых дополнений. В тот момент это было разумно, потому что подпись ставил недействительный сертификат. Оглядываясь назад, кажется, что можно было и оставить эту функцию, но оказалось, что она также конфликтует со смягчением «жёсткая прошивка даты», которую мы обсудим ниже (хотя в конечном итоге мы её не использовали). Поэтому хорошо, что мы сохранили этот вариант. Итак, подписание новых дополнений теперь отложено.
Во-вторых, мы сразу выпустили быстрое исправление, которое подавляет повторную проверку подписей расширений. Идея заключалась в том, чтобы защитить пользователей, которые ещё не прошли повторную проверку. Мы сделали это до того, как у нас было какое-либо другое исправление, а теперь удалили, когда исправления доступны.
Параллельная работа
Теоретически, решение такой проблемы выглядит просто: сделайте новый, действительный сертификат и переиздайте каждое дополнение с этим сертификатом. К сожалению, мы быстро определили, что это не сработает по ряду причин:
- Расширений очень много (более 15 000), и служба не оптимизирована для массовой подписи, поэтому просто повторное подписание каждого дополнения займёт больше времени, чем мы хотели.
- После того, как дополнения подписаны, пользователям будет необходимо получить новое дополнение. Некоторые размещены на серверах Mozilla, и Firefox обновит их в течение 24 часов, но пользователям придётся вручную обновлять любые аддоны, которые установлены из других источников, что очень неудобно.
Вместо этого мы сосредоточились на попытке разработать исправление, которое бы исправляло ситуацию практически без ручного вмешательства со стороны пользователей.
Рассмотрев ряд подходов, мы быстро сошлись на двух основных стратегиях, которые мы проводили параллельно:
- Патч Firefox для изменения даты, используемой для проверки сертификата. В этом случае существующие дополнения волшебным образом снова заработают, но потребуется доставка новой сборки Firefox.
- Сгенерировать новый действительный сертификат и каким-то образом убедить Firefox принять его вместо существующего, просроченного.
Мы не были уверены, что именно сработает, поэтому решили проводить работы параллельно и внедрить первое, что будет похоже на рабочее решение. В конце дня мы завершили деплой второго исправления — нового сертификата, который я опишу более подробно.
Сертификат на замену
Как упоминалось выше, здесь нужно было выполнить два основных шага:
- Создать новый действительный сертификат.
- Удалённо установить его в Firefox.
Чтобы понять, почему это работает, вам нужно знать немного больше о том, как Firefox проверяет дополнения. Само дополнение поставляется в виде пакета файлов, который включает цепочку сертификатов, используемую для его подписания. В результате аддон независимо проверяется, если известен корневой сертификат, который настраивается в Firefox во время сборки. Однако, как я уже сказал, промежуточный сертификат был сломан, поэтому дополнение на самом деле не поддавалось проверке.
Но оказывается, что когда Firefox пытается проверить расширение, он не ограничивается использованием только сертификатов в самом расширении. Вместо этого он пытается создать допустимую цепочку сертификатов, начиная с сертификата конечного объекта и продолжая до корневого каталога. Алгоритм сложный, но на высоком уровне вы начинаете с сертификата конечного объекта, а затем находите сертификат, субъект которого равен эмитенту сертификата конечного объекта (т. е. промежуточного сертификата). В простом случае это только промежуточное звено, поставляемое с надстройкой, но это может быть любой сертификат, о котором знает браузер. Если мы можем удалённо добавить новый, действительный сертификат, Firefox также попытается построить такую цепочку. На рисунке ниже показана ситуация до и после установки нового сертификата.
После установки нового сертификата Firefox имеет два варианта проверки цепочки сертификатов: использовать старый недействительный сертификат (что не сработает) или использовать новый действительный сертификат (что сработает). Важной особенностью здесь является то, что новый сертификат имеет то же имя субъекта и открытый ключ, что и старый сертификат, так что его подпись на сертификате конечного объекта действительна. К счастью, Firefox достаточно умён, чтобы попробовать оба способа, пока не найдёт рабочий, поэтому расширение снова становится действительным. Обратите внимание, что это та же логика, которую мы используем для проверки сертификатов TLS, поэтому это относительно хорошо понятный код, который мы смогли использовать (читатели, знакомые с WebPKI, поймут, что так работает перекрёстная сертификация).
Самое замечательное в этом исправлении заключается в том, что оно не требует изменения каких-либо существующих расширений. Когда мы установим новый сертификат в Firefox, то даже расширения со старыми сертификатами пройдут проверку. Хитрость в поставке нового сертификата в Firefox, что нужно сделать автоматически и удалённо, а затем заставить Firefox перепроверить все расширения, которые, возможно, были отключены.
Normandy и система исследований
По иронии, решением проблемы стал специальный тип расширения под названием system add-on (SAO). Для исследований аудитории (Studies) ранее мы разработали систему под названием Normandy, которая умеет поставлять SAO пользователям Firefox. Эти SAO автоматически выполняются в браузере пользователя. Хотя они обычно используются для проведения экспериментов, но также обладают широким доступ к внутренним API в Firefox. В этом случае важно, что они могут добавить в базу сертификатов новые сертификаты, которые Firefox использует для проверки расширений (техническое примечание: мы не добавляем сертификат с какими-то специальными привилегиями; он получает свои привилегии за счёт подписи корневым сертификатом. Мы просто добавляем его в пул сертификатов, которые могут использоваться Firefox. Таким образом, мы не добавляем новый привилегированный сертификат в Firefox).
Итак, решение здесь — создать SAO, который делает две вещи:
- Устанавливает новый сертификат, который мы сделали.
- Заставляет браузер повторно проверить каждое дополнение, чтобы активировать те, которые отключились.
Но подождите, скажете вы. Дополнения не работают, как же заставить работать SAO? Ну, мы подпишем его новым сертификатом!
Собрать всё воедино… и почему так долго?
Итак, теперь у нас есть план: выпустить новый сертификат, чтобы заменить старый, построить системное дополнение, чтобы установить его в Firefox, и развернуть его в Normandy. Мы начали работу примерно с 18:00 PST в пятницу 3 мая, а отправили исправление в Normandy примерно в в 2:44 ночи, то есть менее чем через 9 часов, а затем потребовалось ещё 6–12 часов, прежде чем большинство пользователей его получили. На самом деле это очень хороший старт, но я видел в твиттере ряд вопросов, почему мы не смогли сделать это быстрее. Существует ряд шагов, которые отнимают много времени.
Во-первых, потребовалось некоторое время для выдачи нового промежуточного сертификата. Как я уже упоминал выше, корневой сертификат находится в аппаратном модуле безопасности, который хранится в автономном режиме. Это хорошая практика безопасности, так как вы очень редко используете корневой сертификат, и поэтому хотите хранить его в безопасности. Но очевидно, это несколько неудобно, когда нужно выдать новый сертификат в чрезвычайной ситуации. Во всяком случае, одному из наших инженеров пришлось ехать в безопасное место, где хранится HSM. Затем было несколько фальстартов, когда мы не смогли выдать правильный сертификат, и каждая попытка стоила часа или двух тестирования, прежде чем мы точно понимали, что делать.
Во-вторых, разработка системы занимает некоторое время. Концептуально всё очень просто, но даже простые программы требуют некоторой осторожности, и мы действительно хотели убедиться, что не ухудшили ситуацию. И перед отправкой SAO нужно было его протестировать, а это отнимает время, особенно с учётом того, что его нужно подписать. Но система подписи была отключена, поэтому нам пришлось искать обходные пути.
Наконец, как только SAO был готов к отправке, потребовалось время на развёртывание. Клиенты Firefox проверяют обновления Normandy каждые 6 часов, и, конечно, многие клиенты находятся в автономном режиме, поэтому распространение апдейта для всех пользователей Firefox прошло не мгновенно. Однако, на данный момент большинство получили апдейт и/или новый релиз, который мы выпустили позже.
Последние шаги
Хотя системный аддон, развёрнутый через систему Studies, должен исправить ситуацию у большинство пользователей, он дошёл не до всех. В частности, для нескольких типов пользователей требуется другой подход:
- Пользователи, отключившие телеметрию или исследования.
- Пользователи Firefox для Android (Fennec), где у нас нет исследований.
- Пользователи последующих сборок Firefox ESR, которые не подпишутся на телеметрические отчеты.
- Пользователи, которые находятся за HTTPS MiTM-прокси, потому что наши системы установки аддонов принудительно закрепляют ключи для этих соединений, что конфликтует с прокси.
- Пользователи очень старых сборок Firefox, до которых система Studies не может достучаться.
Мы ничего не можем сделать с последней группой — им придётся обновиться до новой версии Firefox, потому что старые версии обычно имеют довольно серьёзные неисправленные уязвимости безопасности. Мы знаем, что некоторые люди остались на старых версиях Firefox, потому что хотят запускать расширения старого стиля, но многие из них теперь работают с более новыми версиями Firefox. Для других групп мы разработали патч для Firefox, который установит новый сертификат после обновления. Он также выпущен как новая версия Firefox «с точкой», поэтому люди должны её получить — и, вероятно, уже получили — через обычный канал обновления. Если у вас есть билд из нисходящего потока, вам нужно дождаться обновления от мейнтейнера.
Мы признаём, что ничто из этого не является совершенным. В частности, в некоторых случаях пользователи теряют данные, связанные с аддонами (например, расширение типа «контейнеры с несколькими учётными записями»).
Мы не смогли разработать патч, который позволит избежать этого побочного эффекта, но мы считаем, что в краткосрочной перспективе это лучший подход для большинства пользователей. В долгосрочной перспективе будем искать лучшие архитектурные подходы для решения такого рода проблем.
Уроки
Во-первых, я хочу сказать, что команда здесь проделала удивительную работу: они разработали и отправили исправление менее чем за 12 часов с момента первоначального отчёта. Как человек, который присутствовал на собрании, где это произошло, я могу сказать, что люди работали невероятно усердно в трудной ситуации и что очень мало времени было потрачено впустую.
С учётом сказанного, очевидно, что это не идеальная ситуация, и такое вообще не должно было случиться. Нам явно нужно скорректировать наши процессы, чтобы уменьшить вероятность этого и подобных инцидентов и облегчить их исправление.
На следующей неделе мы проведём формальный разбор полётов и опубликуем список изменений, которые намерены сделать, но пока вот мои первоначальные мысли на этот счёт. Главное, у нас должен быть гораздо лучший способ отслеживать в Firefox статус всех систем, которые являются потенциальной бомбой замедленного действия. Нужно убедиться, что ни одна из них внезапно не прекратит работу. Мы здесь ещё прорабатываем детали, но, как минимум, нам нужно провести инвентаризацию таких систем.
Во-вторых, нужен механизм, чтобы быстро накатывать обновления пользователям, даже когда — особенно когда — всё остальное не работает. Отлично, что мы смогли задействовать систему Studies, но это тоже был не самый совершенный инструмент, который мы ввели в эксплуатацию, и который имел некоторые нежелательные побочные эффекты. В частности, мы знаем, что у многих пользователей включены автоматические обновления, но они предпочли бы не участвовать в исследованиях, и это разумное предпочтение (да что говорить, я сам так настраивал браузер!), но в то же время мы должны иметь возможность пушить обновления. Какими бы ни были внутренние технические механизмы, у пользователей должна быть возможность выбрать обновления (включая исправления), но отказаться от всего остального. Кроме того, канал обновлений должен работать более быстро. Даже в понедельник у нас ещё остались пользователи, которые не забрали исправление или новый релиз, что явно не идеально. Над этой проблемой уже поработали, но этот инцидент показывает, насколько она важна.
Наконец, мы рассмотрим в более общем плане нашу архитектуру безопасности расширений, чтобы убедиться, что она корректно обеспечивает безопасность при минимальных рисках сбоев.
На следующей неделе мы опубликуем результаты более тщательного анализа этой ситуации.