Кто угодно может получать доступ к данным из удалённых форков, удалённых репозиториев и даже приватных репозиториев GitHub. И эти данные доступны всегда. Это известно разработчикам GitHub, и они намеренно спроектировали систему таким образом.

Это настолько огромный вектор атак для всех организаций, использующих GitHub, что мы решили ввести новый термин: Cross Fork Object Reference (CFOR). Уязвимость CFOR возникает, когда форк одного репозитория может получить доступ к требующим защиты данным из другого форка (в том числе и к данным из приватных и удалённых форков). Аналогично Insecure Direct Object Reference, при CFOR пользователи передают хэши коммитов, чтобы напрямую получать доступ к данным коммитов, которые иначе были бы для них невидимыми.

Давайте рассмотрим несколько примеров.

Получение доступа к данным удалённых форков


Возьмём такую часто используемую в GitHub последовательность действий:

  1. Пользователь создаёт форк публичного репозитория.
  2. Далее он выполняет коммит кода в свой форк.
  3. Он удаляет свой форк.


Доступен ли по-прежнему код, коммит которого был выполнен в форк? Вроде бы не должен быть, правда? Мы ведь его удалили.

Но он доступен. И будет доступен всегда. Вне вашего контроля.

В показанном ниже видео мы создаём форк репозитория, коммитим в него данные, удаляем форк, а затем получаем доступ к «удалённым» данным коммита через исходный репозиторий.

Вы можете подумать, что защищены необходимостью знать хэш коммита. Но это не так. Хэш можно узнать, подробнее об этом ниже.

▍ Как часто можно находить данные из удалённых форков?


Достаточно часто. Мы изучили три часто форкаемых публичных репозиторя крупной ИИ-компании, и с лёгкостью нашли 40 валидных ключей API из удалённых форков. Похоже, пользователи применяли такой паттерн:

  1. Создавали форк репозитория.
  2. Прописывали ключ API в файле примера.
  3. Выполняли нужные им действия.
  4. Удаляли форк.


Но ещё хуже, что можно сделать и наоборот:

Доступ к данным удалённых репозиториев


Рассмотрим следующий сценарий:

  1. У вас есть публичный репозиторий GitHub.
  2. Пользователь создаёт форк этого репозитория.
  3. Вы коммитите данные после того, как он создал форк (и он никогда не синхронизирует свой форк с вашими обновлениями).
  4. Вы удаляете репозиторий целиком.


Будет ли код, который вы закоммитили после форка, по-прежнему доступным?

Да.

GitHub хранит репозитории и форки в сети репозиториев, где исходный upstream-репозиторий используется в качестве корневого узла. Когда удаляется публичный upstream-репозиторий, для которого создавались форки, GitHub назначает на роль корневого узла один из downstream-форков. Однако все коммиты из upstream-репозитория всё равно существуют и доступны из любого форка.


В показанном ниже видео мы создаём репозиторий, форкаем его, а затем демонстрируем, что к данным, не синхронизируемым с форком, всё равно можно получить доступ через форк после удаления исходного репозитория.

И это не просто какой-то пограничный сценарий. Вот, что произошло у меня недавно:

Я отправил уязвимость уровня P1 крупной технологической компании, показав, что она случайно закоммитила приватный ключ к аккаунту GitHub сотрудника, имеющего высокий уровень доступа к GitHub всей организации. Компания немедленно удалила репозиторий, но поскольку к нему создавались форки, я всё равно смог получить через форк доступ к коммиту, содержащему конфиденциальные данные, хотя форк никогда не синхронизировался с исходным upstream-репозиторием.

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

Но и это ещё не всё.

Получение доступа к данным приватных репозиториев


Рассмотрим часто используемый процесс вывода нового инструмента в open source на GitHub:

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


Будут ли приватные фичи и связанный с ними код (из шага 2) видны широкой публике?

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

Все коммиты, внесённые в приватный форк после того, как вы сделали upstream-репозиторий публичным, доступны не будут. Это вызвано тем, что изменение видимости приватного upstream-репозитория приводит к созданию двух сетей репозиториев — одной для приватной версии, другой — для публичной.


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

К сожалению, именно так чаще всего пользователи и организации подходят к разработке опенсорсного ПО. Из-за этого возможно непреднамеренное раскрытие конфиденциальных данных и секретов в публичных репозиториях GitHub организации.

Как же получить доступ к данным?


Операции уничтожения в сети репозиториев GitHub (подобные описанным в трёх приведённых выше сценариях) удаляют ссылки на данные коммитов из стандартного UI GitHub и обычных операций git. Однако эти данные всё равно существуют, и к ним можно получить доступ (если знать хэш коммита). В этом заключается связь между уязвимостями CFOR и IDOR — если вы знаете хэш коммита, то сможете напрямую получать доступ к данным, не предназначенным для вас.

Хэши коммитов — это значения SHA-1.


Если пользователь знает хэш SHA-1 конкретного коммита, который он хочет просмотреть, то он может напрямую перейти к этому коммиту в конечной точке: https://github.com/<user/org>/<repo>/commit/<commit_hash>. Он увидит жёлтый баннер с информацией о том, что «этот коммит не принадлежит ни к одной из ветвей этого репозитория и может принадлежать форку вне репозитория».


Откуда взять эти значения хэшей?

Хэши коммитов можно подобрать брутфорсом через UI GitHub, и в особенности потому, что протокол git допускает использование коротких значений SHA-1 при ссылках на коммит. Короткое значение SHA-1 — это минимальное количество символов, необходимое для предотвращения коллизий с хэшем другого коммита. Абсолютно минимальное значение равно 4. Пространство ключей всех значений SHA-1 из четырёх символов составляет 65536 (16^4). Брутфорс всех возможных значений можно выполнить достаточно просто.

Рассмотрим для примера этот коммит из нашего репозитория:


Для получения доступа к этому коммиту пользователи обычно переходят к URL, содержащему полный хэш SHA-1 коммита: https://github.com/trufflesecurity/trufflehog/commit/07f01e8337c1073d2c45bb12d688170fcd44c637.

Но им необязательно знать всё значение SHA-1 из 32 символов, достаточно лишь правильно угадать короткое значение SHA-1, которое в данном случае равно 07f01e.


https://github.com/trufflesecurity/trufflehog/commit/07f01e

Но вот, что ещё более интересно: GitHub раскрывает публичную конечную точку API событий. Можно также запрашивать хэши коммитов в архиве событий, управляемом третьей стороной. Он сохраняет все события GitHub за последний десяток лет вне пределов GitHub даже после удаления репозиториев.

Политики GitHub


Недавно мы отправили отчёт о своих находках в GitHub в рамках его программы VDP. Был получен следующий ответ:


После изучения документации стало абсолютно понятно, что GitHub осознанно спроектировал репозитории таким образом.



https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/what-happens-to-forks-when-a-repository-is-deleted-or-changes-visibility

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

Но для нас проблема заключается в следующем:

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

Последствия


Из этого можно сделать несколько выводов:

  1. Пока существует хотя бы один форк, любой коммит в эту сеть репозиториев (то есть коммиты в upstream-репозиторий или в downstream-форки) будет существовать вечно.
    • Это ещё больше убеждает нас в нашем мнении о том, что единственный способ устранить последствия утечки ключа в публичный репозиторий GitHub — это ротация ключей. Мы потратили много времени на документирование способов ротации ключей для самых популярных утёкших типов секретов, изучите нашу работу здесь: howtorotate.com.
  2. Архитектура репозиториев GitHub нуждается в этих слабостях дизайна и, к сожалению, подавляющее большинство пользователей GitHub никогда не будет разбираться в том, как работает сеть репозиториев, что повредит их защите.
  3. Мы надеемся, что благодаря совершенствованию сканирования секретов мы сможем просканировать все коммиты в сети репозиториев и будем отправлять предупреждения о секретах, которые могут принадлежать не нам (например, они могут принадлежать тому, кто форкнул репозиторий). Это потребует более тщательной сортировки.
  4. Эти три сценария могут выглядеть пугающими, однако это не все способы, которыми GitHub способен хранить удалённые из репозиториев данные. В нашем недавнем посте рассказывается о том, что вам нужно сканировать на наличие секретов и удалённые ветви.

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

Telegram-канал со скидками, розыгрышами призов и новостями IT ?

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


  1. Doomet
    12.08.2024 14:56

    del


    1. pshvetso
      12.08.2024 14:56
      +15

      Форк.
      Ваш ход?


      1. deepmind7
        12.08.2024 14:56
        +4

        Удалил репу без форка.

        Ваш ход?


        1. vitaly_il1
          12.08.2024 14:56
          +5

          Вообще не создал репу


          1. vitaly_il1
            12.08.2024 14:56
            +9

            лучший код - это ненаписанный!


            1. xenon
              12.08.2024 14:56
              +2

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


              1. POPSuL
                12.08.2024 14:56

                Так в нём и багов нет!


                1. nochkin
                  12.08.2024 14:56

                  Баги -- это выдумки суетливых. Надо принимать мир таким, как он есть.


        1. ktim8168
          12.08.2024 14:56

          Склонировал исходники до удаления.
          Ваш ход?


  1. mogrein
    12.08.2024 14:56
    +5

    Через поддержку можно удалить полностью коммит с сенситивными данными -
    https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository#fully-removing-the-data-from-github

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


  1. ogost
    12.08.2024 14:56
    +5

    Если я правильно помню, эту уязвимость нашли ещё в прошлом (позапрошлом?) году и ещё тогда в гитхабе ответили мол это не баг, а фича. Могу ошибаться насчёт сроков, но это давно не новость.


    1. HepoH
      12.08.2024 14:56
      +8

      То что это фича описано в тексте статьи со ссылкой на документацию гитхаба, да и сам текст был опубликован не в разделе новостей ;)


  1. randomsimplenumber
    12.08.2024 14:56
    +4

    любой код, закоммиченный в публичный репозиторий, будет доступен вечно

    Надо исходить из этого. Если процесс удаления контролируется не вами, а сторонним ПО, скорее всего, так и будет.


    1. xenon
      12.08.2024 14:56
      +7

      Да. И правильная стратегия, если в код закоммитили какой-то секрет - не удалять его быстренько, в надежде, что никто не увидел, а менять секрет. Он скомпрометирован, все.

      И для меня задача смена секрета кажется очень простой, а значит - надежной, эффективной. В отличие от непостижимой магии git, которую реально мало кто знает (и этот пост - тому пример. Люди которые действительно знают гит в достаточном для безопасности объеме, над каждой строчкой этой статьи пожимали плечами, мол, ну это же общеизвестно! Думаю, таких было мало).

      Не надо полагаться на науку. Не надо полагаться на магию. Тяжесть - это хорошо. Тяжесть - это надежно. Даже если не выстрелит - этим всегда можно врезать по башке.


      1. mogrein
        12.08.2024 14:56

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


      1. Andrey_Solomatin
        12.08.2024 14:56

        В идеале это когда есть сканер на секреты и тогда нужно секрет тоже удалить, чтобы сканер не ругался.

        > В отличие от непостижимой магии git, которую реально мало кто знает (и этот пост - тому пример.

        Гит тут не причём. Это магия ГитХаба по оптимизации форка.


  1. Ryav
    12.08.2024 14:56
    +4

    Как дела с этим у GitLab и Bitbucket?


  1. meettya
    12.08.2024 14:56
    +1

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


  1. Rebelqwe
    12.08.2024 14:56
    +2

    Решение проблемы с файлом, в котором содержится приватный ключ.

    git filter-branch --force --index-filter \

    'git rm --cached --ignore-unmatch <имя_файла>' \

    --prune-empty --tag-name-filter cat -- --all

    git push origin --force --all

    git push origin --force --tags

    По моему так.

    Hidden text


    1. abramovanton
      12.08.2024 14:56
      +6

      Заклинание разрывающее пространственно временной континуум


    1. IGR2014
      12.08.2024 14:56
      +5

      И всё-же надёжнее просто сменить приватный ключ после компрометации..


  1. netch80
    12.08.2024 14:56
    +1

    Вопрос - сколько времени удалённые данные будут доступны? Я про "вечно" не совсем верю. Больше похоже на умолчание Git - 90 дней. Тогда это даже разумно.


    1. syrslava
      12.08.2024 14:56

      Вы, наверное, про reflog. Он не имеет к проблеме никакого отношения, здесь доступ к коммитам выполняется по хешам коммитов (так или иначе добытым, брутфорс (24 бита всего нужно), трекеры, архивы).

      Так что "вечно" - до тех пор, когда будут удалены все до последнего форки в сети репозиториев.


      1. netch80
        12.08.2024 14:56

        Вы, наверное, про reflog.

        Не только. reflog как пример. Головы удалённых веток и всё, на что они ссылаются, хранятся до ближайшей большой сборки. Я не верю, что на github нет периодического запуска аналога `git gc`.

        Он не имеет к проблеме никакого отношения, здесь доступ к коммитам выполняется по хешам коммитов

        Для того, чтобы коммиты были в репозитории, нужно, чтобы они остались доступны. И вот почему они доступны, когда на них ничего не ссылается уже долгое время - сомнительно. Даже ответ с самого github тут может быть некорректным.

        По-нормальному таки надо сделать тест с проверкой после большой паузы.


        1. syrslava
          12.08.2024 14:56
          +3

          Они доступны, потому что у всей "сети репозиториев" (апстрим и все форки) под капотом один-единственный git-репозиторий, над которым есть слой контроля доступа. Пусть есть репозиторий python/cpython. Ветка master в нём - на самом деле ветка с именем типа python/master. Ветка master в моём форке - на самом деле ветка syrslava/master в том же самом git-графе.

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

          Поэтому фокус в статье и работает. GitHub по id форка (syrslava/cpython) находит внутренний git-репозиторий, в котором есть коммиты всех когда-либо созданных форков python/cpython, и по хешу находит коммит.


          1. netch80
            12.08.2024 14:56
            +1

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

            Да, может быть, что внутри github один репозиторий на всех. Может, там что-то вроде, например, 2^16 репозиториев, хэшированных по микросекунде создания первого оригинала, и клоны включаются к оригиналам. Это всё неважно. Важно то, что это хранилище должно периодически чиститься, иначе в нём будет накапливаться то, что никто никогда использовать не будет - убитые репозитории, целые удалённые юзеры, выброшенные ветки, выброшенные ошибочные коммиты (включая те, где случайно вкоммитили пару десятков гигов промежуточных файлов), и прочая и прочая.

            Я не верю, что там вообще нет очистки. Иначе бы оно давно разрослось до объёмов, которые не выдержит ни одно хранилище в мире. Я думаю, что очистка есть. Когда, как она проводится - их внутреннее дело. Может, они раз в год чистят, или когда вырастет от предыдущего в два раза. Для GC существуют много алгоритмов и критериев. Но хоть когда-то это должно делаться.

            Прежде чем продолжать дискуссию, прошу вас оценить самому необходимость чисток, её периодичность и типовые подходы. Можете начать с реализации команд prune, gc, repack в каноническом клиенте git. Только после этого будет смысл продолжать обсуждение.


            1. syrslava
              12.08.2024 14:56
              +1

              А вас прошу оценить, сколько репозиториев/форков/веток создаются и забрасываются каждую единицу времени по сравнению с количеством репозиториев/форков/веток, о которых кто-то парится достаточно, чтобы их вообще удалять. И можно подумать о принципиальной целесообразности GC при масштабах и политиках гитхаба.


              1. syrslava
                12.08.2024 14:56

                Это, кстати, должно быть не слишком сложно: https://www.gharchive.org/


            1. Sap_ru
              12.08.2024 14:56

              Они накрутили с пул-реквестами, на каждый из которых создаётся скрытая ветка, или что-то такое. В результате эффективный GC там сделать толком не получается.
              Интересно, что у GitLab вполне себе можно запустить GC. Результат виден по освободившемуся месту, доступному для аккаунта.
              Непонятно, как при отсутствии GC считается место у GitHub. Получается, что нет способа реально освободить место.


              1. netch80
                12.08.2024 14:56

                Они накрутили с пул-реквестами, на каждый из которых создаётся скрытая ветка, или что-то такое.

                И что? Ну пусть невыдаленный пулл-реквест хранит ветку. Там только изменения для этого PR и он виден в списке.

                Gerrit вон вообще хранит каждую версию каждого коммита на ревью (хотя можно грубой силой зачистить старые).

                В результате эффективный GC там сделать толком не получается.

                Это всё не помешает. Список существующих голов плюс инкрементальная сборка, которая медленно в фоне хрустит по объектам. Специфика Git меняет только способ доступа к объектам (хэши вместо указателей в памяти), но не остальное, и все методы, наработанные десятилетиями для объектов в памяти в языках с AutoMM (LISP, Java, C#, JavaScript, Lua, тысячи их) - будут работать без принципиальных изменений. (Хм, разве что систему поколений придётся убрать... и то не уверен.)


            1. ris58h
              12.08.2024 14:56

              Иначе бы оно давно разрослось до объёмов, которые не выдержит ни одно хранилище в мире.

              Как думаете, сколько репозиториев/форков/веток/whatever "поместится" в одно десятиминутное 1080p видео на YouTube?


              1. netch80
                12.08.2024 14:56

                С проектами типа https://github.com/presslabs/gitfs - может и 1% "репозитория" не влезть.

                А ведь есть те, кто реально это начал использовать. Ограничения на размер приватного репозитория до сих пор нет, насколько мне известно - только на количество пользователей.


                1. ris58h
                  12.08.2024 14:56

                  Может. А может и нет.

                  У GitHub есть ограничения на размер файла в 100MiB, а большие репы могут вызвать вопросы от тех. поддержки.

                  А ведь есть те, кто реально это начал использовать.

                  Поделитесь ссылками на примеры, пожалуйста. Хочу впечатлиться.


            1. Andrey_Solomatin
              12.08.2024 14:56

              GC отчищает то, на что нет указателей. А в форке есть своя главная ветка.

              То есть коммиты которые не принадлежат больше никакой ветке скорее всего удаляются. А вот коммиты которые в ветке форка будут на месте. Видимо гитхаб не удаляет ветки для удалённых форков.


  1. ITShchen
    12.08.2024 14:56
    +4

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


  1. Kahelman
    12.08.2024 14:56
    +1

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

    Котлы мог подумать ….


    1. Andrey_Solomatin
      12.08.2024 14:56

      Как будето с self-hosted ничего не утекало.


      1. Wesha
        12.08.2024 14:56

        Как будето с self-hosted ничего не утекало.

        Но в этом случае виновного можно было найти и прикопать!