Как стать автором
Обновить
2776.69
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

Как мы взломали цепочку поставок и получили 50 тысяч долларов

Уровень сложностиСредний
Время на прочтение14 мин
Количество просмотров1.8K
Автор оригинала: RONI CARTA | LUPIN

В 2021 году я только начинал свой путь в наступательной безопасности. Я уже взломал довольно много компаний и получал стабильный доход охотой за баг-баунти — практикой этичного хакинга, при которой исследователи безопасности находят уязвимости и сообщают о них, получая за это вознаграждение. Однако я ещё не достиг уровня, позволившего бы мне быстро обнаруживать критические уязвимости цели. Этот уровень умений казался мне недостижимым. Но всё поменялось, когда я познакомился с человеком, ставшим ключевой фигурой в моей карьере баг-баунти: Snorlhax.

Поначалу я видел в нём конкурента. Он был намного выше меня во французской таблице лидеров HackerOne, что стимулировало меня расти над собой. Мы начали общаться в Discord, и спустя несколько недель я рассказал ему о многообещающей программе баг-баунти. Вскоре после этого он обнаружил у этой цели критическую уязвимость, оценённую в 10000 долларов — сумму вдвое больше, чем максимальная полученная мной от этой же цели. Мотивировавшись этим, я вернулся к этой же цели и за ту же неделю нашёл собственную критическую уязвимость за 10000 долларов в другом классе багов.

Вместо того, чтобы продолжать состязаться, мы решили сотрудничать. Теперь нашей задачей стало выявление у этой цели всех возможных классов багов: IDOR, SQL-инъекций, XSS, багов OAuth, Dependency Confusion, SSRF, RCE и так далее. Все их мы нашли, сообщили компании и написали отчёты. Это сотрудничество длилось несколько лет, и даже сегодня мы время от времени снова возвращаемся к этой цели.

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

В посте я расскажу, как мы со Snorlhax наконец-то этого добились.

При помощи атаки на цепочку поставок, позволявшей обеспечить RCE у разработчиков в конвейерах и на продакшен-серверах, мы заработали 50500 долларов.

Анализируем поверхность атаки бизнеса нашей цели


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

У нас был очень простой план: в этой программе баг-баунти приемлемой целью считается любая организация, официально приобретённая основной компанией. Мы считали, что на эти «покупки» легко не обратить внимания, потому что многие охотники делают упор на основные корневые домены. Однако старая инфраструктура купленного бизнеса с редко обновляемыми фреймворками и менее строгими политиками безопасности может стать идеальной средой для серьёзных уязвимостей. Мы чувствовали, что если и есть место, где можно найти чудовищную дыру, то лучшим кандидатом для этого станет купленная компания.

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

Почему мы выбрали цепочку поставок


Нас всегда привлекала мысль о том, что атаку необязательно проводить «в лоб». Часто гораздо эффективнее исследовать ресурсы и сервисы, сводимые компанией в её уязвимые среды — её конвейеры, зависимости, регистры и образы. Если удастся найти уязвимость, то мы сможем вмешиваться в код ещё до того, как он доберётся до продакшена, что нанесёт больше урона, чем стандартные баги SSRF или XSS.

Чтобы разобраться в поверхностях атак на цепочку поставок, нам необходимо исследовать фреймворк SLSA (Supply-chain Levels for Software Artifacts). Он разбивает цепочку поставок ПО на три части: Source, Build и Distribution. Атака на одну из них может вызвать хаос. Мы сразу выбрали Source (например, GitHub, DockerHub и реестры) и Build (конвейеры CI/CD), потому что в них обычно куча токенов, секретов и неправильных конфигураций.


Мы со Snorlhax уже пробовали тестировать атаки на цепочки поставок в других программах и находили безумные уязвимости: возможность доступа к Artifactory, захват почтовых ящиков сотрудников, предоставивших нам доступ к своему Github, Dependency Confusion и многие другие. Однако в этот раз у нас было предчувствие: мы решили, что приобретённая компания, вероятно, использует устаревшие или неконтролируемые процессы цепочек поставок, и это может привести нас к чему-то покрупнее.

Поэтому мы решили объединить две идеи: использовать в качестве цели приобретённую компанию и вмешаться в её цепочку поставок. Мы стремились найти баг, который действительно сможет повысить ставки. Исследование цепочки поставок новоприобретённых компаний — это очень нишевый подход, ниша внутри ниши, и мы были практически уверены, что больше его никто не применял. Это дало нам огромное преимущество.

▍ Как мы приступили к разведке


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

Нам нужно было понять, есть ли у этой дочерней компании онлайн-хранилище кода или она использует какой-то популярный реестр пакетов. Мы выполнили скрейпинг файлов JavaScript из её приложений фронтенда, чтобы посмотреть, какие зависимости кода они вызывают. Вместо рудиментарного поиска строк мы решили выбрать более надёжный подход и преобразовали файлы JS в абстрактные синтаксические деревья (Abstract Syntax Tree, AST).

AST — это древообразное представление исходного кода, разбивающее его части (переменные, функции, импорты и так далее) в иерархические узлы. Воспользовавшись библиотекой SWC (Speedy Web Compiler), мы написали код на Rust, парсящий файлы JS в эти AST и систематически обходящий их, чтобы найти все конструкции import и require.

Это позволило нам чётко идентифицировать ссылки на уникальную область видимости @компания-utils/package. Первым делом мы проверили, можно ли стать владельцами пространства имён npm-организации. Однако выяснилось, что компания уже владеет им, но публичные пакеты там отсутствовали.


Это дало нам понять, что используется как минимум одна приватная npm-организация. На npmjs можно создать организацию, но при покупке лицензии публиковать пакеты приватно. Так что или у компании есть на npm приватные пакеты, или она забронировала пространство имён, чтобы избежать dependency confusion, реализованного через захват пространства имён.

Далее мы проверили, можно ли найти какие-нибудь приватные пакеты этой организации. Для этого мы выполнили следующий Github Search path:**/packages.json @компания-utils:


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

Подсказка: это можно сделать с любыми типами строк, указывающими на использование внутреннего исходного кода. Например, если вы найдёте приватный Artifactory, то можете отправить запрос path:**/package-lock.json artifactory-url.tld, чтобы проверить возможность получения файлов из этого корпоративного Artifactory. Разумеется, вам нужно будет адаптировать запрос под менеджер пакетов (yarn, pip, pnpm и так далее).

К сожалению, мы не смогли найти ничего непосредственно на GitHub, поэтому ввели в Google компания-utils, очень наивно надеясь найти другие артефакты. Мы обнаружили организацию DockerHub, тоже привязанную к бренду этой дочерней компании. Этот новый след как раз подходил нам: окружение, в котором хранятся приватные или плохо защищённые образы Docker.


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

Роемся в исходном коде


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

Обычно обнаружения исходного кода может быть достаточно, чтобы получить вознаграждение за критичный баг, но мы хотели найти «чудовищную уязвимость». Сам по себе исходный код ценен, но мы должны доказать дополнительный урон, продемонстрировав отделу безопасности компании степень опасности раскрытия кода.

Продолжив рыться в коде, мы заметили, что в контейнере сохранилась папка .git/. Это стало нашей следующей подсказкой. Мы надеялись, что изучив файл .git/config, сможем найти URL приватного репозитория или непосредственно переменную окружения. Мы обнаружили нечто, чего раньше никогда не встречали в .git/config: токен авторизации в кодировке base64.

[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
[remote "origin"]
	url = https://github.com/Компания/backend
	fetch = +refs/heads/*:refs/remotes/origin/*
[gc]
	auto = 0
[http "https://github.com/"]
	extraheader = AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46TG9sWW91V2FudGVkVG9TZWVUaGVUb2tlblJpZ2h0Pw==

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

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

Однако, если содержащие токен артефакты (например, файлы .git/config, логи переменных окружения или целые checkout репозиториев) загружаются до завершения цепочки операций, может возникнуть состояние гонки. Этот артефакт может стать доступным всем, у кого есть доступ на чтение до истечения срока действия токена. Если нападающий сможет достаточно быстро скачать артефакт (в нашем случае образ Docker), то у него появится возможность извлечь всё ещё валидный токен и использовать его для изменения или пушинга кода в репозиторий, модифицировать релизы или даже выполнять перенаправление в другие репозитории GitHub в рамках той же организации.

Риск особенно высок, если цепочка операций даёт токену привилегии «write» или «admin» (например, через permissions: contents: write в файле YAML GitHub Actions). В этом случае нападающий получает возможность инъецировать зловредный код, создавать новые ветви и даже выполнять коммиты непосредственно в продакшене. При определённой структуре конвейера CI/CD зловредные изменения могут быстро распространиться даунстрим, потенциально компрометируя приложение, используемое миллионами пользователей.

Во многих современных конвейерах DevOps и Dockerfile и процессы CI/CD тесно переплетены. Широко распространён следующий паттерн:

  1. Checkout в Workflow: цепочка операций GitHub Actions (или любого другого поставщика CI/CD) использует действие наподобие actions/checkout для получения исходного кода. По умолчанию этот этап может включать в себя учётные данные git или токены внутри файла .git/config.
  2. Dockerfile COPY: во время сборки Docker разработчики могут копировать в образ Docker целые папки исходников, в том числе и скрытые папки наподобие .git/.
  3. Публикация образа: собранный образ пушится в публичный или приватный реестр контейнеров. Если конфиденциальные файлы (наподобие .git/ или дампов переменных окружения) не будут очищены, они останутся в более ранних слоях или окажутся в окончательном образе.

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

В нашем контексте важно, сможем ли мы победить в состоянии гонки? Github Workflow (к которому у нас был доступ, потому что мы получили весь исходный код бэкенда) выглядел так:

name: Build and push Docker image
on:
  push:
    tags:
      - '*'

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout codebase
        uses: actions/checkout@v3

      - name: Define image name
        run: |
          echo "IMAGE_NAME=компания/backend" >> $GITHUB_ENV

      - name: Define image tag
        run: |
          if [[ "${{ github.ref }}" == 'refs/tags/'* ]]; then
            echo "IMAGE_TAG=$(git tag --points-at $(git log -1 --oneline | awk '{print $1}'))" >> $GITHUB_ENV
          else
            exit 0
          fi

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build Docker image
        run: |
          source $GITHUB_ENV
          echo IMAGE_NAME=$IMAGE_NAME
          echo IMAGE_TAG=$IMAGE_TAG
          docker build --build-arg NPM_TOKEN=${{ secrets.NPM_TOKEN }} --tag $IMAGE_NAME:$IMAGE_TAG .

      - name: Push image to DockerHub
        env:
          DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
          DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
        run: |
          docker login --username $DOCKERHUB_USERNAME --password $DOCKERHUB_PASSWORD
          docker push $IMAGE_NAME:$IMAGE_TAG

      - name: Deploy to staging
        env:
          DEPLOYMENT_API_SECRET: ${{ secrets.DEPLOYMENT_API_SECRET }}
        run: |
          curl -XPOST 'https://deploy.компания.tld/v1/deploy' \
            -H "Content-type: application/json" \
            --data-raw "{
              \"appName\": \"backend\",
              \"envName\": \"backend\",
              \"contName\": \"backend\",
              \"imageTag\": \"`echo $IMAGE_NAME`:`echo $IMAGE_TAG`\",
              \"secret\": \"`echo $DEPLOYMENT_API_SECRET`\"
            }"

      - name: Post status on Slack
        id: slack
        uses: slackapi/slack-github-action@v1.24.0
        with:
          payload: |
            {
              "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}"
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: "https://hooks.slack.com/services/THE_ACTUAL_SLACK_HOOK_HAHA"
          SLACK_WEBHOOK_TYPE: "INCOMING_WEBHOOK"

Как вы могли заметить, после команды docker push есть ещё два этапа, Deploy to staging и Post status on Slack. На этих этапах токен всё ещё будет доступен, потому что исполняющий эти этапы воркер продолжает работать. Вполне реалистично будет сказать, что нападающий может просто мониторить любые публикации нового образа и скачивать ТОЛЬКО конкретный слой в образе Docker, который содержит токен GHS, чтобы в дальнейшем организовать Post-Exploit репозитория Github.

Примечание: Palo Alto Unit42 опубликовал статью «ArtiPACKED: Hacking Giants Through a Race Condition in GitHub Actions Artifacts», выпущенную примерно спустя месяц после завершения нашего исследования. В статье представлен подробный анализ именно этого типа атаки. Пусть мы и не первыми нажали на «Опубликовать», но это отличная иллюстрация того, как множество разных исследователей безопасности может независимо друг от друга обнаруживать и анализировать одинаковый класс уязвимостей. Поистине, «великие умы мыслят одинаково».

Но нам хотелось большего


Изучив собиравший этот образ Dockerfile, мы заметили package.json, выглядящий примерно так:

{
  "name": "content",
  "version": "1.7.0",
  "private": true,
  "scripts": {
   ....
  },
  "dependencies": {
    "@компания-utils/internal-react": "^4.12.0",
    ...

  },
  "devDependencies": {
   ...
  },
  
}

В package.json присутствовало определение организации @компания-utils, которое мы обнаружили ранее. Однако чтобы выполнить его pull, необходим токен npm. К сожалению, мы не увидели в корне папки файла .npmrc.

Причина заключалась в том, что Dockerfile копировал файл .npmrc, а затем удалял его на последнем этапе сборки. Какое-то время мы думали, что после удаления .npmrc он пропадает навсегда. Мы решили глубже изучить Docker.

Образы Docker собираются при помощи многослойной файловой системы. Каждая команда из Dockerfile (например, FROM, COPY, RUN и так далее) создаёт новый слой. При сборке образа Docker изучает каждую команду, создаёт (или использует заново) слой для этой команды и накладывает его поверх имеющихся слоёв. Слои предназначены только для чтения, поэтому при изменении файла в одном слое Docker добавляет новый слой с «изменениями», а не редактирует имеющиеся слои на месте. Этот процесс повышает эффективность сборок Docker и обеспечивает возможность кэширования, потому что разные образы могут использовать общие слои.

Чтобы исследовать непосредственно эти слои, можно использовать команду docker history <image>, которая создаёт список последовательности команд (и соответствующих слоёв), использованных для сборки образа.

$ docker history hello-world

IMAGE          CREATED         CREATED BY                SIZE      COMMENT
ee301c921b8a   20 months ago   CMD ["/hello"]            0B        buildkit.dockerfile.v0
<missing>      20 months ago   COPY hello / # buildkit   9.14kB    buildkit.dockerfile.v0

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

dive — это удобный инструмент командной строки, не только отображающий слои образа Docker, но и позволяющий конкретно изучать, какие файлы были добавлены, удалены или изменены в каждом из слоёв. Чтобы исследовать образ при помощи dive, нужно установить этот инструмент, а затем выполнить dive <image_to_dive>:

Gif из официального репозитория инструмента

Он предоставляет интерактивный UI, в котором слева показан каждый слой, а справа — их содержимое. Клавишами со стрелками можно просматривать файловую систему в слоях и исследовать изменения между слоями.

dlayer — это ещё один удобный анализатор слоёв Docker. Он может использоваться и в интерактивном, и в неинтерактивном режиме для просмотра содержимого и структуры файлов каждого слоя в образе Docker. Обычно для этого образ Docker сохраняется как файл tar и исследуется при помощи dlayer:

# Передаём сохранённый tar образа Docker непосредственно в dlayer
docker save image:tag | dlayer -i

# Сохраняем образ в файл tar, и затем анализируем его
docker save -o image.tar image:tag
dlayer -f image.tar -n 1000 -d 10 | less

Это может быть особенно удобно, если мы хотим просто передать image.tar, вывести все слои, а затем передать результаты в другой инструмент. Более того, можно использовать код на Go для реализации dlayer в виде библиотеки, что может оказаться полезным при крупномасштабном сканировании и разведке.

Когда мы узнали, что можно получить слои сборки образа Docker, то поняли, что существует высокая вероятность того, что в более раннем слое .npmrc (а значит, и NPM_TOKEN) остался раскрытым. Мы извлекли каждый слой и тщательно их исследовали.

И МЫ ЕГО НАШЛИ!


Мы обнаружили приватный токен npm, дающий доступ на чтение и запись к пакетам @компания-utils. Наши сердца забились сильнее. Мы поняли, что можем запушить зловредный код в один из этих приватных пакетов, которые автоматически будут получены разработчиками, конвейерами и даже продакшен-средами. Так как это приватный пакет, обычный публичные сканеры не обнаружат никакого вмешательства. А поскольку их версии package.json были настроены так, что допускали апгрейды на младшие версии (^4.12.0), мы могли засунуть невыявляемый бэкдор и скомпрометировать каждую среду, использовавшую этот пакет.

На этом этапе мы со Snorlhax уже тряслись от напряжения. Мы понимали, что обнаружили нашу «чудовищную уязвимость». Дело было не только в считывании приватного исходного кода или в похищении одного токена конвейера. Мы могли влиять на всю цепочку поставок ПО, от локальных машин разработчиков до процессов CI/CD и продакшен-серверов. То есть мы нашли потрясающий баг, оправдывающий нестандартно высокое вознаграждение.

Возможный Post-Exploit и влияние бага


Мы тщательно задокументировали каждый шаг. Отдел безопасности компании всегда мотивировал нас идти глубже, чем просто теоретические уязвимости, и показывать реальное влияние багов. Мы рассказали, как нападающий может создать бэкдор в приватный пакет npm, а затем подождать, пока разработчики или конвейеры не выполнят команды npm install. Если разработчики, сами того не зная, соберут или протестируют код при помощи этого инфицированного пакета, то мы потенциально сможем получить доступ к секретам или к другим внутренним системам. В конвейерах CI/CD это может открыть возможности для считывания конфиденциальных переменных окружения, кражи учётных данных или повышения привилегий. Наконец, в продакшене это может привести к широкомасштабной компрометации, если эти контейнеры получают те же обновления пакетов или имеют автоматический процесс развёртывания.

Чтобы подчеркнуть опасность, мы доказали, что с большой долей вероятности никакой внутренний логгинг или мониторинг не отследит такого проникновения на уровне npm, потому что он происходит вне инфраструктуры цели. Давайте поразмыслим над этим. Мы:

  • Получаем образ Docker из Dockerhub.
  • Локально находим токен npm.
  • Публикуем пакет в приватную организацию registry.npmjs.org.

Мы ни разу не взаимодействовали с веб-приложениями нашей цели, и если не считать логов DockerHub и npm, никак нельзя узнать, кто именно скачал образ. Самым «громким» событием, вероятно, может стать публикация пакета npm, но это уже тема для отдельной статьи.

Заключение


После отправки отчёта о наших открытиях компания незамедлительно признала, что это баг, способный вызвать «цепную реакцию», которая может скомпрометировать не только отдельный продукт, но и весь цикл от разработки до вывода в продакшен. Она классифицировала это как редкую уязвимость наихудшего сценария и выплатила нам вознаграждение в 50500 долларов, в несколько раз превосходящее её обычную структуру выплат. Мы со Snorlhax наконец-то нашли нашу «чудовищную уязвимость», ставшую признанием нашей интуиции и всех знаний, накопленных в течение нескольких лет.

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

Мы продолжаем заниматься хакингом вместе, постоянно находясь в поисках новой потрясающей находки. При этом мы надеемся, что наши публикации вдохновят других исследователей двигаться глубже. Иногда истинное сокровище спрятано в тёмных уголках: скрытый образ Docker, неправильно обрабатываемый токен npm или неудалённая папка .git. В этих уголках вы можете найти огромное вознаграждение, как это и произошло в нашем случае.

Telegram-канал со скидками, розыгрышами призов и новостями IT 💻
Теги:
Хабы:
+20
Комментарии0

Публикации

Информация

Сайт
ruvds.com
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
ruvds