November 1, 2023

Кража токена доступа сотрудников GitHub с помощью действий GitHub

GitHub запускает программу вознаграждения за обнаружение ошибок на HackerOne, и в рамках этой программы исследование уязвимостей разрешено «безопасной гаванью» .
В этой статье описывается уязвимость, которую я обнаружил в результате своего расследования в соответствии с критериями безопасной гавани, и она не предназначена для поощрения несанкционированной деятельности по исследованию уязвимостей.
Если вы обнаружите уязвимость на GitHub, сообщите об этом в GitHub Bug Bounty .

ТЛ;ДР

В репозитории action/runner , в котором размещен исходный код средства запуска GitHub Actions, была обнаружена ошибка в использовании автономного средства запуска, которая позволила мне украсть токен личного доступа из GitHub Actions.
Поскольку этот токен был привязан к учетной записи сотрудника GitHub, я мог выполнять различные действия в качестве сотрудника GitHub.
Это потенциально позволяло вставлять вредоносный код в такие репозитории, как action/checkout и action/cache , что могло повлиять на многие репозитории, использующие GitHub Actions.

О самостоятельном раннере

Самостоятельный исполнитель, как следует из названия, — это функция, которая позволяет пользователям запускать средства запуска GitHub Actions на своих машинах. В основном он используется в CI, где существуют требования к оборудованию.
Эта функция достигается путем установки средства запуска Actions на компьютер пользователя. И этот исполнитель не изолирует среду для каждого выполнения, поэтому состояние системы распределяется между заданиями, если только пользователь не изолирует среду отдельно. Такое поведение не является проблемой безопасности, если выполняются только доверенные рабочие процессы.

триггер pull_request

Однако в GitHub Actions есть триггер рабочего процесса под названием pull_request.
Этот триггер выполняется, когда происходит событие, связанное с запросом на включение. При запуске рабочего процесса он считывает файл определения рабочего процесса из разветвленного репозитория 1 и запускает рабочий процесс в контексте базового репозитория, в котором был создан запрос на включение, с переданным токеном, доступным только для чтения.
Это означает, что разветвленный репозиторий может выполнять произвольные рабочие процессы в действиях GitHub в общедоступном репозитории. 2

При использовании средства запуска, размещенного на GitHub, среда изолируется для каждого выполнения рабочего процесса, поэтому проблем с таким поведением нет. Однако в случае использования автономного средства выполнения среда не изолируется для каждого выполнения рабочего процесса. Таким образом, вредоносный запрос на извлечение может выполнить произвольный код на локальном средстве запуска и поставить под угрозу среду.
Это позволяет злонамеренному запросу на вытягивание украсть конфиденциальную информацию (например, токен доступа GitHub с доступом на запись) позже, когда скомпрометированный исполнитель получит ее.

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

Уязвимый рабочий процесс

Теперь давайте посмотрим на реальный рабочий процесс, который был уязвим для этой атаки.
В action/runner существует рабочий процесс для теста E2E под названием e2etest.yml . 3

Этот рабочий процесс выполняет следующие шаги:

  1. Удалите все зарегистрированные локальные исполнители, чтобы очистить завершенный запуск рабочего процесса.
  2. Создавайте раннеры для разных архитектур и операционных систем (Linux/Windows/macOS).
  3. Запустите сценарий, который асинхронно выполняет следующие процессы:
    1. Получите список автономных исполнителей, зарегистрированных в данный момент в репозитории.
    2. Найдите в списке бегун, который следует использовать для теста E2E, и отправьте на бегун соответствующее тестовое задание для типа ОС.
    3. Если все тесты запущены, выйдите из этого процесса. В противном случае продолжайте процесс.
    4. Спите 10 секунд, чтобы избежать ограничения скорости API.
    5. Вернитесь к шагу 3-1.
  4. Чтобы запустить автономную среду выполнения, выполните следующие действия для каждой комбинации архитектуры и типа ОС.
    1. Загрузите исполняемый файл, созданный на шаге 2.
    2. Настройте автономный бегун и зарегистрируйте его в репозитории action/runner.
    3. Дождитесь выполнения задания из сценария, запущенного на шаге 3.
    4. Выполните полученное задание.
    5. Удалите автономный бегун из репозитория action/runner.
    6. Загрузите результаты теста.
  5. После завершения всех тестов проанализируйте загруженные результаты тестов.

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

Проблема этого рабочего процесса

Помните, что в приведенных выше шагах сценарий выполняется асинхронно на шаге 3.
Этот сценарий приостанавливает 10 секунд каждый раз после получения списка зарегистрированных самостоятельных участников, чтобы избежать ограничения скорости.
Другими словами, максимальная задержка составляет немногим более 10 секунд с момента регистрации автономного бегуна на шаге 4-2 до выполнения задания для бегуна на шаге 3-2.

Как About self-hosted runnerпоясняется в этом разделе, автономный исполнитель может получать задания, выполняемые по запросу на включение. Отправив вредоносный запрос на включение в течение этих 10 секунд, произвольные команды могут быть выполнены на локальном бегуне, а последующие шаги будут выполняться на скомпрометированном бегуне.
Глядя на последующие шаги, мы видим, что автономный бегун удаляется из репозитория действий/бегуна на шагах 4–5.
Как правило, токен GitHub, передаваемый исполнителю в действиях GitHub, не имеет разрешения на регистрацию/удаление автономного исполнителя, поэтому они использовали токен личного доступа GitHub для регистрации/удаления самостоятельно размещенного исполнителя, как показано ниже.

.github/workflows/e2etest.yml строка 165 – строка 178

      - name: Configure Runner
        env:
          unique_runner_name: linux-x64-${{needs.init.outputs.unique_runner_label}}
        run: |
          ./config.sh --url ${{github.event.repository.html_url}} --unattended --name $unique_runner_name --pat ${{secrets.PAT}} --labels $unique_runner_name --replace
      - name: Start Runner and Wait for Job
        timeout-minutes: 5
        run: |
          ./run.sh --once
      - name: Remove Runner
        if: always()
        continue-on-error: true
        run: |
          ./config.sh remove --pat ${{secrets.PAT}}

Поскольку задание выполняется на этом Start Runner and Wait for Jobшаге, последующий Remove Runnerшаг будет выполнен на скомпрометированном бегуне.
Это означает, что secrets.PATпереданные бегуну данные ./config.sh remove --pat ${{secrets.PAT}}могут быть украдены путем отправки вредоносного запроса на включение.

Влияние

И теперь вопрос в том, кому принадлежит secrets.PAT.
К счастью, этот токен использовался в другом месте для выполнения рабочего процесса, поэтому было легко определить владельца токена.

Итак, я затем проверил профиль пользователя и обнаружил, что пользователь принадлежит @actions и @github .
Как упоминалось выше, этот токен используется для нескольких целей, поэтому предполагается, что он имеет как минимум определенную public_repoобласть действия.
Следовательно, украв этот токен, можно получить разрешение на запись в публичные репозитории @actions и @github .
Это может привести к серьезной проблеме в цепочке поставок, поскольку @actions и @github владеют множеством репозиториев, включая официальные действия, такие как action/checkout и action/cache .

Заключение

В этой статье я объяснил, как неправильная настройка GitHub Actions может привести к серьезному риску в цепочке поставок.
Надеюсь, эта статья помогла вам понять важность безопасности в конвейере CI/CD.

Если у вас есть вопросы или комментарии, пишите мне в Твиттере ( @ryotkak ) или Misskey ( @ [email protected] ).

График

Дата (JST)

Событие

2021/06/19

Обнаружение уязвимостей/отчетность

2021/06/19

Временное исправление

2021/06/22

Постоянное исправление

2022/12/18

Начать писать эту статью

2023/04/11

Получил разрешение на публикацию этой статьи

2023/04/22

Опубликовал эту статью


  1. Если быть более конкретным, он использует файл определения рабочего процесса во время фиксации слияния запроса на включение. ↩︎
  2. Точнее, из-за этого изменения рабочий процесс не будет запускаться автоматически, если вы хотя бы один раз не внесли свой вклад в целевой репозиторий. ↩︎
  3. Я опускаю код, поскольку он содержит более 300 строк, но вы можете увидеть файл рабочего процесса здесь: https://github.com/actions/runner/blob/a9be5f65578a8225c6a024799f572ad2066c4fd8/.github/workflows/e2etest.yml ↩︎