Двадцать четвёртого апреля 2026 года в репозиторий пакетов PyPI была загружена вредоносная версия популярной библиотеки elementary-data (0.23.3), предназначенной для наблюдения за данными в среде инструмента dbt. Одновременно с этим в реестре контейнеров GitHub Container Registry был опубликован заражённый многоплатформенный образ с тегами 0.23.3 и latest, что затронуло все системы, использующие некорректно настроенные команды docker pull или инструкции FROM без указания фиксированного digest. Инцидент представляет собой классический пример атаки на цепочку поставок открытого кода, когда злоумышленник получает контроль над процессами сборки и публикации легитимного проекта.
Описание
Событие развивалось стремительно. На момент написания заметки вредоносная версия всё ещё значилась на PyPI как последняя, а сам образ оставался в GitHub Container Registry. Специалисты по информационной безопасности из компании StepSecurity, первыми зафиксировавшие инцидент, опубликовали детальный анализ произошедшего, в котором описали механизм эксплуатации уязвимости в составе GitHub Actions рабочего процесса (конвейера автоматизации непрерывной интеграции и доставки) разработчиков elementary-data.
Механизм атаки: от комментария к подписанному коммиту
Злоумышленник не взламывал репозиторий напрямую, а использовал уязвимость внедрения скрипта в одном из файлов рабочего процесса - ".github/workflows/update_pylon_issue.yml". Внутри этого файла на шаге "Extract Issue or Pull Request Details" содержимое комментария к запросу на слияние (pull request) напрямую подставлялось в блок "run:" без какой-либо санитизации. Такая практика - распространённая ошибка, приводящая к тому, что злоумышленник может вставить в комментарий произвольные команды оболочки, которые выполняются на агенте GitHub Actions.
Двадцать первого апреля на одном из открытых запросов на слияние номер PR #2147 (автоматически созданном легитимным пайплайном для синхронизации документации) появился комментарий от только что зарегистрированного аккаунта realtungtungtungsahur. Вместо обычного текста комментарий содержал команду "curl | bash", которая загружала и исполняла на агенте стейджер (вспомогательный скрипт). Выполнившись, этот скрипт с помощью токена GITHUB_TOKEN, который был в области видимости у запущенного рабочего процесса, создал фиктивный коммит в репозитории вне основной ветки master. Сообщение коммита - "release/v0.23.2 (#2188)" - было скопировано из реального, не связанного с данной версией запроса на слияние, введённого девятью днями ранее. Зелёная метка "Verified" на этом коммите означала, что GitHub автоматически подписал его своим ключом как принадлежащий учётной записи github-actions[bot], что создало видимость легитимности.
Сам оригинальный рабочий процесс, спровоцированный комментарием, не завершился быстро - он завис на два часа сорок шесть минут, маскируя основную активность злоумышленника. Под прикрытием этого длительного выполнения злоумышленник вызвал API GitHub для диспетчеризации легитимного рабочего процесса "Release package", указав тег v0.23.3. Поскольку инструкция "checkout" в этом пайплайне использовала аргумент "ref: ${{ inputs.tag || github.ref }}", агент загрузил именно тот поддельный коммит. Результатом стала успешная публикация пакета на PyPI и контейнерного образа в реестр.
Вредоносная полезная нагрузка: трёхступенчатый сборщик секретов
Ключевым отличием вредоносной версии 0.23.3 от предыдущей 0.23.2 стало добавление файла "elementary.pth" в корень устанавливаемого пакета. Файлы с расширением .pth автоматически обрабатываются интерпретатором Python при запуске: любая строка, начинающаяся с "import", выполняется как код ещё до загрузки самого модуля. Таким образом, полезная нагрузка срабатывала при каждом запуске Python в среде, где была установлена скомпрометированная библиотека, независимо от того, импортировал ли код elementary-data явно.
Сам файл содержал цепочку из трёх ступеней раскодирования. Первый слой представлял собой base64 (кодирование бинарных данных в текстовый формат). Затем следовало два этапа расшифровки с помощью XOR-ключа на основе MD5-хеша. Первый ключ-затравка - строка "swabag" - вёл на вторую ступень, а второй ключ, содержавший контактную фразу "for any questions: contact 050afbe046d7545f5af1a0d3fcfbaf6e993fd93d487b431f09bc9e963c7220a135 on session", открывал третий, финальный слой.
Третья ступень представляла собой полноценного вора учётных данных. Сначала он извлекал из локальной файловой системы всю идентифицирующую информацию: секретные ключи SSH, содержимое файлов "authorized_keys" и "known_hosts", учётные данные из "~/.git-credentials", а также токен авторизации "gh auth token". Затем сборщик обращался к облачным API: для AWS он использовал учётные данные из файла и метаданные IMDSv2 (второй версии службы метаданных экземпляров), после чего подписал вызовы SigV4 к сервисам AWS Secrets Manager и SSM Parameter Store для извлечения всех секретов, хранящихся в управляемых хранилищах. Аналогичные действия выполнялись для Google Cloud (файл "application_default_credentials.json") и Azure (каталог "~/.azure"). Отдельно обрабатывались конфигурации контейнеров: "~/.docker/config.json", "~/.kube/config", все файлы из каталога "/etc/kubernetes/*.conf", токены учётных записей сервисов Kubernetes, а также запускалась команда "kubectl get secrets --all-namespaces".
Кроме того, сборщик проходил по всем файлам ".env*" на глубину до шести уровней вложенности, а также собирал конфигурации диспетчеров пакетов и инструментов: ".npmrc", ".pypirc", ".cargo/credentials.toml", ".vault-token" (от HashiCorp Vault), ".netrc", ".pgpass" (пароли к PostgreSQL), ".my.cnf" (клиент MySQL). Особое внимание было уделено криптовалютным кошелькам: перечень включал Bitcoin, Litecoin, Dogecoin, Zcash, Dash, Monero, Ripple (сохранённые конфигурации и файлы "wallet*.dat"), Ethereum (keystore), Cardano (ключи), Solana (две пары ключей валидатора и идентификатора в файлах "validator-keymap.json" и "id.json"), а также ключи для фреймворка Anchor. Системные файлы - "/etc/passwd", "/etc/shadow", истории команд оболочки и лог аутентификации "/var/log/auth.log" - завершали список. Все собранные данные упаковывались в архив "trin.tar.gz" и отправлялись одним POST-запросом через curl на домен "igotnofriendsonlineorirl-imgonnakmslmao.skyhanni.cloud" с обязательным заголовком "X-Rise-To-The-Trinny: agree".
Последствия для пользователей и что можно сделать
Поскольку тег "latest" контейнерного образа был перезаписан, все команды "docker pull ghcr.io/elementary-data/elementary" без явного указания digest, а также инструкции "FROM" в Dockerfile, ссылающиеся на незакреплённую версию, начиная с 24 апреля загружали именно скомпрометированный образ. Это особенно опасно для организаций, использующих автоматическое развёртывание через Kubernetes, Argo CD или Docker Compose, где тег по умолчанию часто остаётся "latest". Утечка секретов затронет не только облачные провайдеры, но и внутренние системы, что может привести к полной компрометации инфраструктуры.
Компания StepSecurity предлагает несколько превентивных мер, которые следует внедрить всем, кто использует сторонние пакеты из PyPI и контейнерные образы. Во-первых, активировать блокировку исходящих подключений на агентах сборки - например, с помощью утилиты Harden-Runner, которая в данном случае предотвратила бы отправку данных на командный центр (C2). Во-вторых, использовать проверки на этапе запросов на слияние, блокирующие внесение свежеопубликованных версий пакетов (так называемый "охлаждающий период"). В-третьих, всегда использовать фиксированные digest для контейнерных образов в рабочих конфигурациях, а не полагаться на теги.
Этот инцидент в очередной раз напоминает, что атаки на цепочку поставок становятся всё изощрённее. Злоумышленники больше не довольствуются прямым взломом репозиториев, а используют уязвимости в конвейерах сборки, которые остаются вне поля зрения большинства разработчиков. Единственный способ защиты - многоуровневый подход: контроль версий зависимостей, аудит рабочих процессов GitHub Actions, ограничение сетевого доступа на агентах и постоянный мониторинг аномалий в публикуемых артефактах.
Индикаторы компрометации
- Compromised PyPI package: elementary-data==0.23.3
- Last clean PyPI version: 0.23.2
- Compromised container image: ghcr.io/elementary-data/elementary:0.23.3 and :latest (both at digest sha256:31ecc5939de6d24cf60c50d4ca26cf7a8c322db82a8ce4bd122ebd89cf634255; multi-arch linux/amd64 + linux/arm64)
- Last clean container image: ghcr.io/elementary-data/elementary:0.23.2 (digest sha256:b3bbfafde1a0db3a4d47e70eb0eb2ca19daef4a19410154a71abee567b35d3d9)
- Injection file: elementary.pth at the package root (single base64-wrapped line, ~245 KB)
- Git tag: v0.23.3 → commit b1e4b1f3aad0d489ab0e9208031c67402bbb8480 (orphan; not reachable from any branch)
- C2 / exfiltration domain: igotnofriendsonlineorirl-imgonnakmslmao.skyhanni.cloud
- Exfiltration header: X-Rise-To-The-Trinny: agree
- Exfiltration archive: trin.tar.gz (created in temp dir; auto-cleaned post-upload)
- Persistent execution marker: $TMPDIR/.trinny-security-update
- Stager URL (expired): https://litter.catbox.moe/iqesmbhukgd2c7hq.sh
- Attacker GitHub account: realtungtungtungsahur (created April 22, 2026)
- Actor contact (Session ID): 050afbe046d7545f5af1a0d3fcfbaf6e993fd93d487b431f09bc9e963c7220a135