Привет, Хабр!

Представь себе ситуацию:

Пятница. Конец рабочего дня. Релиз готов, пройдены последние тесты. Кнопка «Опубликовать» нажимается, и релиз растекается по серверам обновлений и дальше по серверам клиентов.

Суббота. Саппорт завален тикетами вида: «После вчерашнего обновления при формировании отчета XXX выскакивает картинка Warhammer 40k. Эта фича не была заявлена в обновлениях и в документации не описана».

Воскресенье. Чат продукта после диагностики проблемы.

Тимлид: Женя, ты зачем закоммитил картинку с warhammer40k?!
Женя: Да я вообще в отпуске вторую неделю, ты чего, какой hammer40k?
Тимлид: Ну вот же, смотри, в понедельник от тебя коммит через webide!
Женя: Всю неделю провел в тайге. Ел мох и грибы. Какой коммит, спутниковой связи даже не было!
Тимлид: Похоже, это неЖеня от имени Жени...

Как убедиться в том, что коммиты в продуктовых репозиториях «настоящие», то есть отправлены тем человеком, имя которого указано в коммите? Мы с коллегами из команды DevOps задались целью построить процесс, который будет давать нам полностью прозрачную картинку, и у нас это получилось. Эта статья довольно практическая, и решение, о котором я, Рамазан Ибрагимов, вместе с моим коллегой Александром Паздниковым пишу в этом материале, — лишь часть большой схемы по обеспечению безопасности. В качестве хранилища кода будем опираться на инстанс GitLab On-Premise внутри компании — вендора ПО. Будем рады обсудить, как подобные задачи решаете в своей инфраструктуре вы. 

Знакомимся. Злоумышленник и его цели

Итак, перед нами злоумышленник. Он прошел внешний периметр защиты компании, закрепился внутри сети и получил доступы одного из разработчиков. Зачем ему это? Дело в том, что в большинстве случаев исполняемый код попадает в кодовую базу репозиториев продукта через коммиты разработчиков с их рабочих машин.

Злоумышленник хочет:

  1. Добавить вредоносный или открывающий уязвимость код в релиз продукта компании.

  2. Через продукты компании развить атаку на инфраструктуру клиентов.

  3. Реализовать недопустимые события для клиентов.

В итоге получаем крайне негативный веерный сценарий. Такой сценарий трудно отслеживается и детектируется средствами мониторинга, потому что выглядит «настоящим» и нормальным.

Вернемся все же к злоумышленнику, который уже внутри сети компании. Как он сможет (и сможет ли?) влить вредоносный код на корпоративный GitLab On-Premise? Основных сценариев три:

  1. Push нужного ему кода через веб-интерфейс GitLab.

  2. Push нужного ему кода по протоколу HTTPS в репозиторий с использованием выписанного через UI токена.

  3. Push нужного ему кода по протоколу SSH в репозиторий с использованием добавленного через UI ключа.

А что же git? А у git для проверки достоверности коммитов есть механизм подписи коммитов. GitLab умеет задним числом обрабатывать запушенные коммиты и отмечать коммиты с проверенными подписями статусом Verified в своей WebUI. Только это слабо помогает, потому что в процессе CI/CD требуется проверять, что кодовая база отмечена статусом Verified. А что делать, если Verified на деле оказался совсем не Verified?

Таким образом, проверка коммитов на достоверность задним числом, после попадания в GitLab-репозиторий, добавляет кучу головоломок и сценариев на обработку на стороне CI/CD.

Как изначально быть уверенными, что весь код в GitLab-репозиториях гарантированно имеет статус Verified в подписях коммитов разработчиков? Принимать только подписанный код в GitLab-репозитории и отвергать неподписанный.

Как защититься от лжекоммитов

Отвергать прием неподписанных коммитов

Первым приходит на ум каким-то образом обязать разработчиков подписывать код. Для этого будем проверять подписи коммитов в момент пуша в GitLab-репозитории при помощи Git server hooks. По умолчанию такого хука у GitLab нет. Написать самим выглядит посильной задачей. Решаемо.

Блокировать возможность использовать поддельные ключи

Исключаем возможность подделать пары ключей для подписи, выпустив новую пару ключей и прикинувшись разработчиком. Простой механизм GnuPG нам для этого не подходит. Публичная часть находится в профиле разработчика и аналогична добавлению SSH public key для идентификации пользователя при git push через SSH. Можно выпустить новую пару GnuPG, подложить в профиль разработчика и подписывать и пушить код от его имени нелегитимной парой GnuPG. Заметить и задетектить такой коммит крайне сложно.

Зато есть альтернатива GnuPG — это цепочки сертификатов PKI X.509. Развернув свой центр сертификации (CA), мы сможем выписывать сертификаты для конкретных людей и проверять подписи по цепочке доверия до корневого сертификата CA вместо доверия конечной паре ключей подписи. В итоге злоумышленнику нужно выпустить новый сертификат на нашем CA или украсть с рабочей машины разработчика его ключ. И тот и другой сценарий выполнить незаметно на порядок сложнее. Решаемо.

Управлять выпуском сертификатов

Хотим держать под полным контролем небольшой группы людей создание новых пар ключей для подписи коммитов. Для GnuPG придется создать какой-то свой keyring или аналогичное хранилище допустимых публичных частей проверки подписи. А в PKI X.509 это решено из коробки.

При компрометации сертификата легко отозвать сертификат через стандартный CRL в PKI X.509.

Отзывать сертификат ключа подписи коммитов

Для решения задачи контроля выпуска сертификатов и их отзыва хорошо подходит стандартный PKI X.509.

Ну и вишенкой на торте стала фича в GitLab 12.8+ проверки подписей коммитов через корневой сертификат X.509 и отображения статуса Verified для коммитов в GitLab WebUI.

Проверять подписи коммитов

Все нужные нам сценарии по управлению сертификатами решены в техстеке PKI X.509. Что же мы сделали: 

  1. Подняли свой CA.

  2. Подготовили скрипты для выписывания сертификатов конечных разработчиков.

  3. Написали глобальный Git server hook для проверки подписей всех коммитов в пуше по стеку PKI X.509 и установили его на инстанс GitLab. Для проверки знаем только корневой сертификат и проверяем дальше сертификаты конечных пользователей в коммитах, а также автоматом и CRL на отзыв сертификата.

  4. Сконфигурировали GitLab нужным образом для проверки коммитов и отображения статуса Verified через корневой сертификат CA.

  5. За управление выписыванием/отзывом сертификатов отвечает небольшая централизованная группа сотрудников в ИТ. Просто так выписать дублирующий сертификат уже не получится.

Общая схема работы

Упрощенная схема работы процесса
Упрощенная схема работы процесса

Как мы внедрили выбранное решение

Нужно было раздать под тысячу сертификатов. Разработчики используют различные ОС, иногда достаточно экзотические для нас дистрибутивы Linux. Если Debian/Ubuntu и Windows мы смогли протестировать на себе и отладить большинство нюансов, то с редко встречающимися у нас дистрибутивами Mint, Arch, Fedora и другими помогали разбираться сами пользователи этих систем и сводить в FAQ для таких же пользователей. Всего было выписано уже более двух тысяч сертификатов.

Сначала ввели проверку подписей на месяц в рекомендательном характере. Выявили массу нюансов конкретных ОС и зафиксировали основной FAQ по решению подписи на конкретных версиях ОС.

Затем уже вводили блокирующую проверку подписей, и, в принципе, все прошло достаточно гладко.

Из минусов — потерялась возможность изменений через GitLab Web IDE. Такова плата за безопасность кодовой базы.

Скоро уже будет год как все коммиты в продуктовые репозитории подписываются сертификатами разработчиков X.509. Мы расширили информационные части самого хука, чтобы было сразу понятна причина отказа и как ее чинить самому пользователю.

Количество обращений с подписями коммитов и сертификатами через 2-3 месяца начало стремиться к нулю (очень редко случается, что коллеги бывают невнимательны, но мы работаем над этим).

Исключения

Да, конечно, нам пришлось ввести исключения в проверки подписей. И эти исключения всегда рассматриваются комитетом безопасности по допустимости рисков. В исключения попадают только репозитории, не участвующие в кодовой базе продуктов, и только если с подписями ну совсем никак не получается работать. Например, для определенных не ИТ-ролей: юристов, финансистов и других, далеких от git clone/commit/push. В коде серверного хука это белый список.

Настройки для вашего GitLab On-Premise

Как видите, пока что проверка подлинности коммитов работает не «из коробки». Когда-нибудь GitLab добавит эту фичу, по меньшей мере запрос на нее уже есть. А пока вы можете взять кодовую базу нашего Commit Signature Verifier и инструкции по его настройке тут. Пользуйтесь на здоровье и делайте свою кодовую базу заведомо безопасной.

Комментарии (13)


  1. xxxphilinxxx
    02.11.2023 09:04
    +3

    Если злоумышленник уже захватил машину разработчика, как поможет подпись коммитов, отправленных с этой же машины? Или с любой другой, куда утянули сертификат. Сертификат уже скомпрометирован и точно так же надо сверять историю действий. Имхо единственный сценарий из ваших трех, который закрывает этот механизм, это третий: когда злоумышленник все так же находится уже внутри периметра, все так же смог получить учетку гитлаба, но не смог получить доступ к машине с сертификатом.


    1. icq8680 Автор
      02.11.2023 09:04
      +1

      Всё верно. Такой сценарий компрометации машины разработчика злоумышленником этой схемой не покрывается.

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

      Улучшать ещё много чего есть.

      Формулировку мы поправили - злоумышленник в нашем сценарии попал внутрь сети через доступный снаружи ресурс.


      1. Shaman_RSHU
        02.11.2023 09:04

        Можно настроить политики MaxPatrol EDR, чтобы он блокировал кражу закрытой части. Т.к. EDR может работать автономно, то и участие SOC не понадобится :)


  1. olegtsss
    02.11.2023 09:04
    +2

    Использование того же yubikey позволит решить проблему компрометации рабочего места с ключом + безопасность секретного ключа (в том числе от копирования и не легитимного использования) и обойтись без инфраструктуры PKI. Минус - придется потратиться на индивидуальные ключи, зато на перспективу. Window, Linux, Mac - главное, чтобы были необходимые разъемы на рабочем месте (usb, например).


    1. 0mogol0
      02.11.2023 09:04

      эээ, лет десять назад видел попытку использования карт доступа. Карта была воткнута в ридер на машине админа (не разработчика) и не уверен, что вынималась хотя бы вечером. Т.е. опять же если взломщик получил доступ к рабочей машине - то проблема остается.

      Ну или надо заставлять разработчика вынимать ключ после каждого коммита... Что тоже нетривиальная задача, а главное задалбывает как длинные пароли.


      1. olegtsss
        02.11.2023 09:04

        Решается пин кодом (или нажималкой).


        1. 0mogol0
          02.11.2023 09:04

          тогда зачем нужна карта или ключ? не проще в этом случае запрашивать через ОС ещё раз аутентификацию по лицу (в винде точно есть, думаю и в маке тоже) или ввод пина?

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


          1. Heggi
            02.11.2023 09:04
            +1

            Пин, который вводится в системе, точно так же может утечь и вводиться злоумышленником. А вот физическую кнопку на yubikey удаленно никак не нажать.


            1. olegtsss
              02.11.2023 09:04

              Мне кажется интегрировать нажималку (как и auth по лицу) в имеющейся софт может быть ооочень не легко. Вот даже для ssh есть нюансы со старыми Debian-ами, с которыми серверов в проде (или в других местах) хватает. А вот пин работает повсеместно. На самом деле требовать вводить пин раз за разом - вполне реализуемая задача. А вот насчет утечки пина - насколько это реально проблема, хотелось бы услышать мнение специалистов, потому как еще не слышал про такие атаки (типа злой код узнал пин от воткнутого в комп yubikey и давай на повал ходить по серверам из .known и добавлять свой зладейский ключ в .ssh/). Кстати не плохая тема для исследования и Proof of concept ).


              1. Heggi
                02.11.2023 09:04

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


                1. olegtsss
                  02.11.2023 09:04

                  Юбик-то умеет. Не весь софт поддерживает нужные протоколы. Вот я начал про ssh. Чтобы попасть на сервер по ключу нажатием, нужна поддержка соответствующего протокола в openssl, по-моему. Я как-то раз начал прикручивать нужную версию на 9 Debian и сломал все на нем. Потом несколько дней чинил)) и от идеи отказался. Кто знает, GitHub поддерживает нажималку?


                  1. vanyas
                    02.11.2023 09:04

                    Для нажималки не нужна какая-то внешняя поддержка, это фича юбикея, он просто ничего не делает (не подписывает и т.д.) пока его не потрогаешь, софт про это ничего не знает, софт запросил у юбикея подписать что-то, и юбикей ответит только после нажатия кнопки


                    1. olegtsss
                      02.11.2023 09:04

                      Расскажите это ssh и gpg, а также всем, кто работает на их демонах.