Безопасность автообновления - почему одного HTTPS недостаточно
· Го Комура · Разработка Windows, Безопасность, Updater, Автообновление, Подпись, MSIX, ClickOnce
Оглавление
- Сначала вывод
- Почему автообновление - опасная зона
- Антипаттерны
- Лучшие практики
- Минимальная безопасная конфигурация
- Как подходить к этому в Windows-проектах
- Минимальный чек-лист
- Итог
- Источники
1. Сначала вывод
Сначала перечислим, к чему обычно приходят на практике.
- Если требования позволяют, в первую очередь опираться на существующую инфраструктуру обновлений, такую как MSIX App Installer или ClickOnce
- Если нужен собственный updater, первым делом реализовать не UI, а проверку подписи и восстановление после сбоя
- Информацию об обновлении вроде
latest.jsonрассматривать не как неподписанный конфигурационный файл, а как подписанные metadata - TLS необходим, но не является достаточным условием
- Решение об обновлении основывать не на том, что «так сказал сервер», а на том, что «клиент проверил и убедился в корректности»
- Разделять ключи подписи для разработки и продакшена, защищать их HSM или сервисом подписи
- При сбое обновления использовать fail-closed, а не fail-open
- Updater без защиты от отката версии безопаснее считать уязвимым к возврату на старую версию
- Если проверку подписи пока нельзя внедрить, ручное распространение подписанных инсталляторов безопаснее автообновления
Иными словами, суть автообновления не в том, «как скачивать», а в том, «чему доверять, где проверять и как восстанавливаться при поломке».
2. Почему автообновление - опасная зона
Обычные функции остаются в пределах приложения. Updater же одновременно берёт на себя следующие три вещи.
- Забирает файлы извне
- Доверяет этим файлам
- Заменяет уже существующие исполняемые файлы
Иными словами, путь к выполнению произвольного кода изначально встроен в продукт.
Распространённое заблуждение здесь - «раз HTTPS, значит безопасно». Разумеется, TLS необходим. Но он защищает главным образом канал передачи и легитимность конечной точки. Этого недостаточно, если скомпрометирован сам сервер обновлений, на легитимный CDN попал неверный артефакт или подменён неподписанный manifest.
Действительно, даже если посмотреть только на угрозы, систематизированные в TUF, у систем обновления их немало.
- Подсовывание произвольного вредоносного ПО
- Rollback - откат на уязвимую старую версию
- Freeze - сокрытие новой версии
- Mix-and-match - смешивание взаимно несогласованных metadata и артефактов
Иными словами, автообновление - это не «передача файлов», а «распространение доверия». Только спроектировав эту сторону, автообновление начинает работать безопасно.
3. Антипаттерны
Сначала соберём опасные схемы, часто встречающиеся на практике.
| Антипаттерн | В чём опасность | Минимальное исправление |
|---|---|---|
Забирать version.json по HTTPS и сразу запускать zip / exe по указанному URL |
Уязвимо к компрометации origin, подмене конфигурации, ошибочной доставке | Перейти на клиентскую проверку подписанных metadata и артефактов |
| Подписан только бинарный файл, manifest не подписан | Можно подделать URL, version, channel, флаг обязательного обновления | Использовать signed manifest с version / hash / size / channel / expiry |
| Ключ подписи хранится в файле на dev-машине или в CI | При компрометации можно распространить легитимно подписанное вредоносное ПО | HSM / сервис подписи + процесс согласования + журнал аудита |
| При сбое обновления «игнорировать ошибку проверки и продолжить» | В момент инцидента открывается самый слабый путь | Использовать fail-closed |
| Обновление перезаписью без сохранения старой версии | Потеря питания, нехватка диска, сбой посередине приводят к неработоспособности | staging + атомарная активация + rollback |
| Допускать старую версию только по сравнению номеров версий | Проходит rollback на уязвимую версию | Монотонно возрастающая release version и хранение максимальной известной версии |
| Запускать весь updater с правами администратора | Широкий радиус поражения при компрометации | Загрузка/проверка с низкими правами, замена вынесена в минимальный helper |
| Начинать с дифференциальных обновлений | Реализация сложна, растёт число пропусков при проверке | Начинать с обновления полным пакетом |
Далее рассмотрим чуть подробнее.
3.1 Останавливаться на «раз HTTPS, значит всё в порядке»
Это встречается чаще всего.
- При запуске читается
latest.json - Извлекается
downloadUrl - Скачивается zip / exe
- Распаковывается и заменяет старые файлы
- Готово
Выглядит правдоподобно, но корень доверия слишком сильно опирается на ответ сервера. Если скомпрометирован сервер обновлений или конфигурация доставки, вредоносное обновление можно раздать поверх абсолютно корректного HTTPS.
TLS необходим. Но одним TLS проектирование updater не заканчивается.
3.2 Подпись есть, но клиент её не проверяет
Даже если файлы подписаны на этапе релиза, это ничего не значит, если клиент не проверяет подпись.
Часто встречается такая картина:
- в CI файлы подписываются;
- но updater смотрит только на hash;
- а сам этот hash берётся из неподписанного manifest.
В этом случае в момент подмены manifest вместе с ним подменяется и hash. Тезис «мы проверяем hash, значит безопасно» верен только тогда, когда защищено и происхождение самого hash.
3.3 Manifest не подписан
В системе обновления по-настоящему нужно защищать не только сам исполняемый файл. Как минимум опасно, если можно подделать следующую информацию:
- version / release id;
- URL или имя файла загрузки;
- hash / size;
- channel (stable / beta и т. д.);
- признак обязательности обновления;
- применимую ОС / архитектуру;
- срок действия metadata;
- минимально необходимую версию updater.
Иными словами, правильный ориентир - включать в signed metadata всю информацию, используемую для принятия решения об обновлении.
3.4 Небрежное обращение с ключом подписи
Безопасность функции обновления в значительной степени сводится к безопасности управления ключами.
Если продакшн-ключ подписи хранится следующим образом, это довольно опасно:
- остаётся в хранилище сертификатов на dev-машине;
- загружен как
.pfxв виде CI secret; - один и тот же приватный ключ распространён локально среди нескольких людей;
- подпись для разработки и продакшена использует одну и ту же цепочку доверия.
В этом случае даже корректный сам по себе updater не сможет остановить «легитимно подписанное вредоносное обновление».
3.5 Обновление перезаписью без сохранения старой версии
Для обновления важнее спроектировать сценарий сбоя, чем сценарий успеха.
- загрузка прервалась на середине;
- распаковка завершилась ошибкой;
- питание пропало во время замены файлов;
- новая версия запустилась, но упала при первой миграции.
Если в этот момент старая версия уже удалена, восстановление становится тяжёлым. На практике проблемой оказывается не сам факт «обновление не удалось», а то, что «приложение перестало запускаться на месте эксплуатации».
3.6 Отсутствие мыслей о rollback
Даже подписанная легитимная версия может быть выгодна атакующему, если это старая уязвимая версия.
Например:
- в version 1.8 есть известная уязвимость;
- на местах уже установлена 2.3;
- атакующий повторно распространяет 1.8.
Если это проходит, ситуация опасна, хотя подпись сама по себе корректна.
Недостаточно проверять только «подписано ли это»; нужно ещё проверять «допустимо ли устанавливать именно эту версию сейчас».
3.7 Fail-open
Это самое недопустимое, что можно сделать в продакшене.
- при неудачной проверке подписи выводится только предупреждение, а работа продолжается;
- существует скрытый флаг, позволяющий игнорировать ошибку истечения срока сертификата;
- отладочный
skipVerify=trueостаётся включённым и в продакшене.
Именно в момент сбоя или атаки такие лазейки становятся основным путём атаки.
4. Лучшие практики
4.1 Сначала опереться на существующую инфраструктуру обновлений
Безопаснее сначала усомниться в том, действительно ли нужен собственный updater.
Для Windows, пока требования позволяют, в первую очередь стоит рассмотреть:
- MSIX + App Installer;
- ClickOnce;
- Store / MDM / внутреннюю инфраструктуру распространения;
- MSI + корпоративное управление распространением.
Причина проста: часть ответственности за само обновление можно переложить на платформу. Свобода действий, конечно, снижается, но зато легче обеспечить согласованность UI обновления, distribution manifest, подписи пакета и эксплуатации.
Собственный updater становится нужен, например, в таких случаях:
- нужен строгий контроль нескольких каналов stable / beta / preview;
- нужно поэтапное развёртывание и доля rollout;
- нужен тонкий контроль момента обновления по собственным бизнес-причинам;
- есть конфигурация, не укладывающаяся в MSIX / ClickOnce.
Даже в этом случае полезнее понимать это не как «хотим свободы», а как «берём на себя ответственность за обновление» - так меньше шансов сбиться с курса.
4.2 Разместить точку отсчёта доверия на стороне клиента
Безопасный updater не принимает ответ сервера на веру. На стороне клиента необходимы как минимум две вещи.
- Доверенный публичный ключ или цепочка сертификатов
- Механизм проверки metadata, подписанных этим ключом
Иными словами, нужно создать состояние, в котором клиент может убедиться не в том, что «сервер утверждает, что это последняя версия», а в том, что «эти metadata - последняя версия, выпущенная доверенным подписантом».
4.3 Проектировать вокруг signed metadata
Как минимум в metadata обновления нужно включить следующее и сделать это частью подписываемых данных.
| Пункт | Зачем включать |
|---|---|
| release version / release id | Защита от rollback, аудит |
| имя artifact, URL, package type | Фиксирует, какой именно файл будет получен |
| hash, size | Обнаружение подделки, обнаружение повреждённой доставки |
| channel | Не допускать смешивания beta со stable |
| целевая ОС / architecture | Защита от ошибочной доставки |
| minimum updater version | Останавливает старые updater при изменении протокола |
| expires_at | Защита от freeze |
| published_at | Аудит, разбор инцидентов |
| mandatory / optional | Делает неподделываемым даже ветвление UX обновления |
Важно здесь - собрать все данные, на основе которых принимается решение об обновлении, в signed metadata. Логика остаётся на стороне клиента, а подлинность информации защищается подписью - такой подход снижает число инцидентов.
4.4 Проверять и сам артефакт
После проверки metadata нужно проверить и загруженный артефакт по следующим пунктам:
- size;
- hash;
- подпись пакета / подпись кода;
- издателя или ожидаемый идентификатор.
При работе с Windows PE / MSI / MSIX безопаснее исходить из того, что проверка Authenticode или подписи пакета выполняется на стороне клиента. На macOS для согласованности стоит исходить из того, что Developer ID и notarization также применяются к пути обновления.
4.5 Защищать ключи эксплуатацией, а не функциональностью
Разница в управлении ключами проявляется не столько в реализации, сколько в эксплуатации.
Как минимум безопаснее разделить:
- ключ подписи для разработки;
- ключ подписи для staging;
- ключ подписи для продакшена.
А для продакшена желательно предусмотреть ещё и:
- HSM;
- облачный сервис подписи;
- систему подписи с процессом согласования;
- журналы аудита;
- процедуру ротации ключей;
- подпись с timestamp.
Вариант «CI автоматически подписывает, если прошла продакшн-сборка» удобен, но при компрометации увеличивает радиус поражения. Как минимум стоит обеспечить возможность отследить, кто, что и когда подписал.
Когда эксплуатация становится зрелой, ещё безопаснее разделить редко меняемый root trust и ключ, которым часто переподписываются метаданные обновления. Дизайн, при котором root остаётся преимущественно offline, а для метаданных обновления используется отдельный ключ, легче снижает радиус поражения при компрометации ключа.
4.6 Fail-closed и поэтапное обновление
Базовый порядок потока обновления таков.
- Получить metadata
- Проверить подпись, срок действия и version
- Скачать артефакт в область staging
- Проверить hash / size / подпись
- Подготовить активацию, сохранив старую версию
- Переключиться при перезапуске или через выделенный helper
- Проверить работоспособность при первом запуске
- При проблеме выполнить rollback
Важны здесь два момента: не заменять файлы до завершения проверки; не продолжать при сбое.
4.7 Ограничивать права updater
Стоит избегать запуска всего updater с правами администратора.
Идеальное разделение выглядит так:
- загрузка и проверка: низкие права;
- только замена файлов: helper с минимальными правами;
- helper не делает ничего сверх «поместить проверенный package в назначенное место».
Чем больше дизайн требует повышения привилегий, тем опаснее он становится, если не разграничить чётко, что именно проверено до повышения.
4.8 Устранять rollback / freeze / mix-and-match с самого начала
Добавлять это задним числом тяжело, поэтому лучше заложить сразу.
-
Защита от rollback Клиент хранит «максимальную из ранее виденных metadata version / release version» и отклоняет всё, что старше.
-
Защита от freeze У metadata есть срок действия, слишком старые metadata отклоняются.
-
Защита от mix-and-match Части metadata согласованы между собой. Как минимум в самом manifest зафиксированы hash / size / version целевых артефактов.
Кроме того, если через signed metadata можно распространить blocklist конкретных build или minimum allowed version, локализация инцидента происходит быстрее.
Даже без полного внедрения TUF эти три свойства очень важны.
4.9 Начинать с полного обновления
Дифференциальные обновления экономят трафик, но для первой реализации они слишком сложны.
- к какой именно паре «старая версия → новая версия» относится дельта;
- предварительный hash до применения дельты;
- итоговый hash после применения дельты;
- восстановление при сбое посередине;
- очистка частично применённых или устаревших дельт.
Всё это разом накапливается. Для первой версии достаточно безопасно заменить полный подписанный пакет.
5. Минимальная безопасная конфигурация
Даже если не доводить до полного TUF, минимальная безопасная конфигурация собственного updater обычно выглядит так.
5.1 Что хранит клиент
- доверенный root-публичный ключ или закреплённую цепочку сертификатов;
- текущую работающую version;
- максимальную из ранее виденных metadata version / release version;
- разрешённый channel;
- предыдущую версию для rollback.
5.2 Что возвращает сервер
- подписанные update metadata;
- артефакты, подписанные разработчиком или платформой;
- при необходимости - информацию blocklist / minimum allowed version.
5.3 Типичный поток
Получить metadata
↓
Проверить подпись, expiry, version, channel
↓
Скачать артефакт в staging
↓
Проверить size / hash / package signature
↓
Активировать, сохранив старую версию
↓
Выполнить rollback при сбое первого запуска
Важно здесь то, что сам по себе ответ сервера обновлений ничего не устанавливает. Устанавливают это trust anchor, хранящийся на клиенте, и логика проверки.
6. Как подходить к этому в Windows-проектах
Для Windows-приложений проще всего выстраивать логику, отталкиваясь от способа распространения.
- если требования подходят - MSIX App Installer;
- если это внутреннее .NET-приложение, для которого подходит per-user - ClickOnce;
- если нужны службы, драйверы, shell extension или собственный контроль каналов - в сравнение стоит включить и MSI + собственный updater.
Однако даже при выборе собственного updater объём работы не уменьшается. Скорее наоборот, он растёт.
- проверка Authenticode / подписи пакета;
- signed manifest;
- защита от rollback;
- разделение прав update helper;
- стратегия обновления самого updater.
Типичная опасная схема в Windows - прямая цепочка DownloadFile -> unzip -> kill process -> overwrite -> restart.
Она может работать, но слаба и с точки зрения безопасности, и с точки зрения восстановления.
Практика, при которой пользователей заставляют проходить предупреждения SmartScreen или UAC через «Подробнее → Выполнить в любом случае», - это не проектирование обновления, а приучение к игнорированию предупреждений. Если строить правильный путь обновления, нужно не приучать пользователей игнорировать предупреждения, а выстраивать конфигурацию распространения и проверки, при которой предупреждения возникают редко.
Сравнение самих способов распространения мы разбирали и в другой статье: Как выбрать способ распространения Windows-приложения - таблица решений MSI / MSIX / ClickOnce / xcopy / собственный updater
7. Минимальный чек-лист
Перед выпуском собственного updater стоит проверить как минимум следующее.
- Metadata обновления подписаны
- В metadata есть version / hash / size / channel / expiry
- Клиент проверяет подпись и version
- Проверяются hash и подпись платформы для артефакта
- Продакшн-ключ подписи отделён от среды разработки
- Ведутся журналы использования ключа и записи согласования
- Используется подпись с timestamp
- При staging-обновлении переключение происходит с сохранением старой версии
- Есть условия и процедура rollback
- При сбое проверки применяется fail-closed
- Есть политика обновления самого updater
- Можно распространить blocklist / minimum allowed version
- Есть kill switch для остановки поэтапного развёртывания
- Можно наблюдать частоту сбоев, частоту rollback, сбои проверки подписи
Если в этом чек-листе много пустых пунктов, эффективнее сначала проработать модель доверия распространения, а не UI updater.
8. Итог
В конечном счёте безопасность функции автообновления сводится к следующему.
Проектировать не удобство обновления, а то, кому доверять и как клиент проверяет это доверие.
Исходя из этого, практические выводы можно сформулировать так.
- Если существующей инфраструктуры достаточно, сначала опереться на неё
- Если создаётся собственный updater, подписанные metadata и управление ключами нужно внедрить раньше, чем беспокоиться об HTTPS
- Updater без спроектированного восстановления после сбоя и rollback тяжело эксплуатировать в продакшене
- Updater - это не функция распространения, а сама граница безопасности продукта
Если текущая конфигурация близка к latest.json + замена zip, первое, что стоит исправить, - не логику загрузки, а то, на чём строится доверие.
Уже одно это исправление заметно меняет уровень риска.
9. Источники
- CISA Secure by Design Pledge
- NIST: Security Considerations for Code Signing
- NIST Secure Software Development Framework (SSDF)
- The Update Framework Specification
- TUF: Roles and metadata
- TUF: Security
- Microsoft Learn: Authenticode Digital Signatures
- Microsoft Learn: Auto-update and repair apps - MSIX
- Microsoft Learn: ClickOnce Deployment and Security
- Apple Developer: Developer ID
- CA/Browser Forum: Baseline Requirements for the Issuance and Management of Publicly-Trusted Code Signing Certificates
Похожие темы
Страницы тем, близких к этой теме. От статьи можно перейти к связанным услугам и другим статьям.
Технические темы по Windows
Точка входа, объединяющая технические темы по разработке Windows, расследованию сбоев и использованию существующих активов.
Услуги, связанные с этой темой
Разработка Windows-приложений
Автообновление - это не только вопрос UI, а проектирование, охватывающее способ распространения, права доступа, восстановление и эксплуатацию. Мы можем помочь как с новой разработкой Windows-приложения, так и с пересмотром существующего ПО, начиная с упорядочивания подхода к обновлению.
Техническая консультация и ревью архитектуры
К нам можно обратиться уже на этапе постановки вопроса: «нужен ли собственный updater», «достаточно ли MSIX / ClickOnce», «в чём риск текущего дизайна обновления».
Профиль автора
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, технической консультации и расследовании неисправностей, особенно силён в проектах, связанных с сохранением существующих активов, и в расследовании сбоев, причины которых трудно установить.
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Почему Windows показывает сообщение «Windows защитил ваш компьютер»
Разбираем с практической точки зрения, почему при распространении Windows-приложений появляется предупреждение SmartScreen: подпись кода,...
Как выбрать способ распространения Windows-приложения — MSI/MSIX/ClickOnce/xcopy/собственный updater
Способ распространения Windows-приложения — это не вопрос вкуса в выборе формата установщика, а выбор степени связанности с ОС и ответств...
CI/CD для приложений WinForms / WPF на практике — автоматизация от сборки до подписи и распространения через GitHub Actions
Практическое руководство по настройке CI/CD для приложений WinForms / WPF через GitHub Actions. Минимальный YAML для сборки и тестов на w...
Если ваше Windows-приложение приняли за вирус — как реагировать на ложные срабатывания Microsoft Defender и жить с влиянием на производительность
Разбираем правильный порядок действий, если Microsoft Defender ложно определяет ваше Windows-приложение как вредоносное: как устроена сов...
Что такое ClickOnce - механизм работы, обновления и случаи применения с практической точки зрения
Разбираем ClickOnce - технологию распространения .NET-приложений для Windows: манифесты, обновления, кэш, подпись, а также случаи, где он...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Распространение, обновление, подпись, откат версий и выбор между MSIX и ClickOnce для Windows-приложений требуют продумывания не только реализации, но и способа распространения, и эксплуатационного проектирования.
Технические консультации и ревью дизайна
Проектирование границы доверия автообновления, подписанных метаданных, эксплуатации ключей и логики fail-closed важнее прорабатывать как упорядочивание общей архитектуры, чем как отдельную реализацию.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Достаточно ли того, что автообновление работает по HTTPS, чтобы считать его безопасным?
- TLS необходим, но не является достаточным условием. TLS защищает главным образом канал передачи и легитимность конечной точки, но не помогает, если скомпрометирован сам сервер обновлений, на легитимный CDN попал неверный артефакт или подменён неподписанный manifest. Решение об обновлении должно приниматься не потому что «так сказал сервер», а потому что «клиент проверил подписанные метаданные и убедился в их корректности».
- Что нужно включить в metadata обновления и подписать?
- Базовый принцип - собрать все данные, на основе которых принимается решение об обновлении, в подписанные метаданные (signed metadata). Конкретно это: release version, URL и имя артефакта, hash и size, channel (stable / beta и т. д.), целевую ОС и архитектуру, минимальную версию updater, срок действия metadata (expires_at), а также флаг обязательности обновления. Если подписан только бинарный файл, а manifest остаётся неподписанным, остаётся возможность подделать URL, версию или флаг обязательного обновления.
- Что такое атака отката версии (rollback) и как от неё защититься?
- Это атака, при которой злоумышленник повторно распространяет старую, но легитимно подписанную версию с известной уязвимостью, вынуждая откатиться на неё. Поскольку сама подпись корректна, одной только проверки подписи недостаточно. В качестве защиты клиент должен хранить максимальную из ранее виденных release version и отклонять всё, что старше неё. Вместе с этим стоит задать срок действия для metadata, чтобы защититься от freeze-атаки, скрывающей новые версии, и зафиксировать в manifest hash, size и version артефактов, чтобы исключить mix-and-match атаки.
- Стоит ли писать собственный updater или лучше использовать готовые механизмы?
- Если требования позволяют, безопаснее в первую очередь опираться на существующую инфраструктуру обновлений, такую как MSIX App Installer или ClickOnce, поскольку это перекладывает значительную часть ответственности за обновление на платформу. Собственный updater нужен, когда есть требования, не укладывающиеся в существующую инфраструктуру, - например, строгий контроль нескольких каналов или поэтапное развёртывание. Но и в этом случае в первую очередь нужно реализовать не UI, а проверку подписи и восстановление после сбоя: соблюдать fail-closed (не продолжать при неудачной проверке) и обеспечить возможность отката за счёт сохранения предыдущей версии при переключении.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.
Публичные ссылки