NPM-червь вернулся, и стало еще хуже (взлом TanStack)

BBetter Stack
컴퓨터/소프트웨어경영/리더십AI/미래기술

Transcript

00:00:00Шай-Хулуд вернулся с четвертым сиквелом.
00:00:02В этот раз он нацелился на такие пакеты, как TanStack,
00:00:04буквально через несколько часов после выхода моего видео о Next.js,
00:00:07что было просто гениальным выбором времени с моей стороны.
00:00:08Это уже масштабная атака на цепочку поставок NPM,
00:00:11затронувшая не только TanStack.
00:00:13Она также поразила такие пакеты, как UiPath, Mistral,
00:00:15и еще 160 других пакетов,
00:00:17включая даже пакеты PyPy, например Guardrails.ai.
00:00:20Что делает этот случай еще интереснее,
00:00:22так это наличие «кнопки мертвеца»:
00:00:24если обнаруживалось, что вы сменили украденные ключи,
00:00:26он стирал весь ваш компьютер,
00:00:28а еще в него была встроена глобальная политика.
00:00:30Итак, давайте погрузимся.
00:00:36В этом сиквеле у «Червя» та же цель:
00:00:39кража учетных данных с машин разработчиков и CI/CD-раннеров,
00:00:42затем использование этих данных для доступа к другим пакетам.
00:00:44Для TanStack это означало публикацию 84 вредоносных версий
00:00:47в 42 пакетах TanStack всего за несколько минут.
00:00:51Сейчас я расскажу, как им удалось
00:00:52инфицировать TanStack в первую очередь,
00:00:54но сначала посмотрим, что делает сам вредонос,
00:00:56если вы установите один из этих скомпрометированных пакетов.
00:00:58Внутри вредоносных пакетов
00:00:59вы найдете новый файл под названием routerinit.js,
00:01:02а также внедренную опциональную зависимость,
00:01:04которая ведет на то, что выглядит как
00:01:05легитимная ссылка на GitHub-репозиторий TanStack router,
00:01:08но на самом деле это «осиротевший» коммит в форке злоумышленника.
00:01:10Это просто особенность обработки ссылок на форки в GitHub,
00:01:13поэтому URL может выглядеть так,
00:01:14будто он принадлежит оригинальному проекту,
00:01:16даже если коммит сделан из форка.
00:01:18В этом форке есть скрипт жизненного цикла,
00:01:20prepare, который запускает bun run task runner JS
00:01:22и завершается с кодом exit 1.
00:01:24Это хитрый способ заставить опциональную зависимость упасть
00:01:27уже после выполнения полезной нагрузки,
00:01:28так что установка продолжается как обычно,
00:01:30и в логах установки остается меньше очевидных следов.
00:01:33Также вы могли заметить, что здесь не запускается
00:01:35тот файл routerinit.js, о котором я говорил ранее,
00:01:37внедренный в пакеты,
00:01:38но пока просто воспринимайте эти два файла
00:01:40как выполняющие одну и ту же роль под разными именами.
00:01:42Вкратце: когда вы устанавливаете это,
00:01:44скрипт начинает выполняться.
00:01:46Первое, что пытается сделать скрипт —
00:01:47отвязаться от очевидного процесса установки,
00:01:50поэтому он проверяет, работает ли он уже
00:01:51в фоновом режиме, и если нет,
00:01:53он создает отсоединенную копию себя
00:01:54и аккуратно завершает родительский скрипт.
00:01:57Благодаря этому в логах npm
00:01:58не будет вывода скрипта,
00:02:00потому что вредонос уже отсоединился
00:02:01от этого процесса и работает в фоновом режиме.
00:02:04Затем он делает нечто действительно умное.
00:02:06Он записывает копии самого себя
00:02:07в директорию хуков Claude Code,
00:02:08а затем настраивает ваши параметры Claude,
00:02:10чтобы запускать этот хук каждый раз, когда вы используете Claude Code
00:02:12в этом проекте.
00:02:13Таким образом, он может выжить после первоначальной установки
00:02:16и продолжать перезапускаться каждый раз,
00:02:17когда вы открываете Claude Code в этом проекте.
00:02:20Он делает то же самое с планировщиком задач VS Code,
00:02:22дублируя себя там,
00:02:23так что если вы используете функции автоматического запуска рабочих областей VS Code,
00:02:26у вас возникает та же проблема.
00:02:28Он даже устанавливает системную службу ОС
00:02:29под названием GitHub Token Monitor,
00:02:31но мы вернемся к ней позже,
00:02:32потому что она абсолютно дьявольская.
00:02:34Также довольно дьявольски то,
00:02:35что вы еще не подписались.
00:02:37Следующее, что делает вредонос —
00:02:38он принимается за кражу ваших учетных данных,
00:02:40и пытается украсть всё.
00:02:41В GitHub Actions он ищет учетные данные
00:02:43и секреты в среде раннера.
00:02:45А точнее, сканирует память рабочего процесса
00:02:47раннера GitHub Actions
00:02:48в поисках секретов вашего рабочего процесса,
00:02:50включая замаскированные секреты,
00:02:52и даже вставляет поддельный GitHub workflow, похожий на CodeQL,
00:02:55который сериализует секреты вашего репозитория
00:02:57и позже их эксфильтрует.
00:02:58Он также ищет секреты AWS,
00:03:00сначала охотясь за переменными окружения
00:03:02и локальными конфигурационными файлами,
00:03:03а затем атакует метаданные AWS,
00:03:06такие как IMDS v2 и метаданные задач ECS.
00:03:09Для Kubernetes он крадет токены сервисных аккаунтов
00:03:11и сертификаты, что дает ему доступ к API внутри кластера
00:03:14с теми правами доступа (RBAC),
00:03:17которые были у сервисного аккаунта этого пода,
00:03:19что в плохо сконфигурированных кластерах
00:03:21может быть очень широким доступом,
00:03:22иногда фактически права администратора.
00:03:24И что еще хуже,
00:03:25он также охотится за HashiCorp Vault,
00:03:27собирая все переменные окружения,
00:03:29связанные с Vault, и токены,
00:03:30затем использует имеющийся у него доступ к Kubernetes,
00:03:32чтобы извлечь все ваши секреты, хранящиеся в Vault.
00:03:34И все это только то,
00:03:35что он делает с вашими CI-развертываниями.
00:03:37Если он на вашей рабочей станции,
00:03:38он охотится за всеми вашими SSH-ключами,
00:03:39вашими учетными данными NPM,
00:03:41вашими учетными данными Git,
00:03:42историей оболочки,
00:03:43учетными данными облачных провайдеров,
00:03:44криптоключами,
00:03:45файлами Signal,
00:03:45Slack
00:03:45и Discord.
00:03:46И в дополнение ко всему этому,
00:03:47он извлекает историю сеансов Claude Code.
00:03:49Так что, если вы когда-либо давали Claude учетные данные
00:03:51или позволяли ему читать файлы, содержащие учетные данные,
00:03:53у него есть доступ и к ним.
00:03:55Так что да, как я и сказал,
00:03:56они охотились абсолютно за всем,
00:03:57до чего могли дотянуться,
00:03:58а затем они эксфильтровали эти данные
00:04:00через сеть мессенджера Session.
00:04:02А в качестве резервной копии
00:04:02они также сбрасывали эти украденные данные
00:04:04в репозитории GitHub.
00:04:05И в тему всех их атак,
00:04:07эти ветки названы в честь отсылок к “Дюне”.
00:04:09Итак, у него есть ваши учетные данные.
00:04:11Хуже уже не может быть, верно?
00:04:12Ну, да.
00:04:13Да, может.
00:04:14В дополнение ко всему этому,
00:04:15если помните тот сервис,
00:04:16который, как я сказал, он устанавливает на вашей машине,
00:04:18тот самый мониторит ваши токены GitHub
00:04:19и продолжает их переэксфильтрацию.
00:04:21Но также каждую минуту
00:04:22он проверяет, действителен ли токен.
00:04:24И если нет,
00:04:25он запускает `rm -rf` в вашем домашнем каталоге,
00:04:27удаляя все.
00:04:28Он также пытается создать токен NPM
00:04:30с вашими учетными данными,
00:04:31с описанием:
00:04:32«Если вы отзовете этот токен,
00:04:33мы сотрем компьютер владельца»,
00:04:35подразумевая, что он делает то же самое
00:04:36и для токенов NPM.
00:04:38Так что, если вы отзовете эти токены
00:04:39до того, как изолируете свою машину
00:04:40и удалите этот фоновый процесс,
00:04:42полезная нагрузка может самоуничтожить ваш ПК,
00:04:44что просто абсолютно дьявольски.
00:04:46И в качестве примечания:
00:04:47Python-вариант этой атаки
00:04:48делает примерно то же самое,
00:04:49но он также включает проверку,
00:04:51является ли язык вашей машины русским.
00:04:53Если да,
00:04:53он просто останавливается.
00:04:54А если ваша машина, похоже,
00:04:55из Израиля или Ирана,
00:04:56он генерирует случайное число
00:04:58от 1 до 6.
00:04:59И если это число 2,
00:05:00он запускает команду деструктивной очистки
00:05:01и пытается на полной громкости воспроизвести
00:05:03MP3-файл.
00:05:04К сожалению,
00:05:05я не смог выяснить,
00:05:05что это за MP3.
00:05:07В любом случае,
00:05:07теперь, когда он сделал все это,
00:05:08худшее еще впереди,
00:05:09потому что это был только первый этап.
00:05:11Второй этап — самораспространение,
00:05:13и это самая опасная часть
00:05:15этой атаки.
00:05:16Сначала
00:05:16он будет искать на вашей машине
00:05:17любые действительные токены NPM,
00:05:19с помощью которых он может публиковать
00:05:19без двухфакторной аутентификации.
00:05:21И если он найдет такой,
00:05:22он просканирует все пакеты,
00:05:24к которым у этой учетной записи есть доступ,
00:05:26затем использует эти учетные данные,
00:05:26чтобы добавить себя в эти пакеты
00:05:28и опубликовать новые зараженные версии.
00:05:30Это, очевидно, довольно плохо,
00:05:32но вам также, вероятно, не следовало
00:05:33оставлять опубликованные токены,
00:05:33которые могут обойти
00:05:34которые могут обойти
00:05:35двухфакторную аутентификацию.
00:05:36Так что гораздо более пугающий вариант этого
00:05:38— это то, что происходит,
00:05:39когда это запускается внутри вашего CI/CD.
00:05:41Потому что в CI
00:05:42злоумышленнику не нужен
00:05:43долгоживущий NPM-токен,
00:05:44потому что хорошие настройки
00:05:45часто полагаются на OIDC,
00:05:47который задумывался как более безопасный.
00:05:48По сути,
00:05:49вместо хранения
00:05:50NPM-токена как секрета,
00:05:51GitHub Actions доказывает NPM,
00:05:53эй,
00:05:53я этот репозиторий,
00:05:54запускающий этот рабочий процесс
00:05:55в этой ветке,
00:05:56и NPM тогда выдает ему
00:05:57краткосрочный токен для публикации.
00:05:59Проблема с этим, однако,
00:06:00в том, что если скрипт получает доступ
00:06:01к доверенной среде GitHub Actions,
00:06:03он может занять то же место,
00:06:04что и легитимный издатель.
00:06:06Так что вредоносное ПО может использовать
00:06:07связанное с OIDC окружение,
00:06:08которое GitHub предоставляет заданию,
00:06:10чтобы запросить OIDC JWT-токен
00:06:12из конечной точки токенов GitHub,
00:06:14затем обменивает этот JWT-токен
00:06:16с NPM
00:06:17на краткосрочный токен для публикации
00:06:18через доверенную систему
00:06:19публикации NPM,
00:06:20и теперь он может публиковать
00:06:22без кражи
00:06:22постоянного NPM-токена,
00:06:24и выглядеть полностью легитимным.
00:06:26В этом случае
00:06:26вредоносное ПО упаковывает копию
00:06:27того файла router init.js
00:06:29в таблицу пакета,
00:06:30затем добавляет вредоносную
00:06:31необязательную зависимость,
00:06:32затем публикует это всё
00:06:33как последний тег
00:06:34для этого пакета,
00:06:35так что, когда кто-то
00:06:35или какой-то CI/CD конвейер
00:06:37устанавливает эти пакеты,
00:06:38цикл начинается заново,
00:06:40распространяясь настолько далеко,
00:06:40насколько это возможно.
00:06:42Так что это всё
00:06:42довольно безумно, верно?
00:06:43Но теперь давайте сфокусируемся
00:06:44на пациенте номер ноль,
00:06:46TanStack.
00:06:46Как они были заражены
00:06:47в первую очередь?
00:06:48Что ж,
00:06:49согласно их собственному постмортему,
00:06:50злоумышленник злоупотребил
00:06:51этим конвейером GitHub Actions.
00:06:53Они начали за день
00:06:53до того, как вредоносные пакеты
00:06:54были фактически опубликованы,
00:06:56где они создали форк
00:06:57TanStack router,
00:06:58но они фактически переименовали
00:06:59его в configuration,
00:06:59чтобы попытаться сделать его сложнее
00:07:01для поиска, если вы искали
00:07:02среди очевидных названий форков.
00:07:04Затем они добавили
00:07:04вредоносный коммит
00:07:05к этому форку,
00:07:06который они поддельно авторствовали
00:07:07как Claude,
00:07:07и у него было сообщение коммита,
00:07:08которое было снабжено префиксом
00:07:09skip CI,
00:07:10чтобы он не запускал немедленно
00:07:11CI при событии push.
00:07:13На следующий день
00:07:13они открыли PR
00:07:14против TanStack router,
00:07:15названный Work in Progress
00:07:16Simplify History Build.
00:07:18И именно здесь
00:07:18происходит сама атака.
00:07:20TL;DR заключается в том, что
00:07:21у TanStack был рабочий процесс GitHub Actions
00:07:22для размера бандла,
00:07:23который использовал pull_request_target,
00:07:25и это примечательно,
00:07:26потому что pull_request_target
00:07:27на самом деле запускается
00:07:28в контексте безопасности
00:07:29базового репозитория,
00:07:30а не форка.
00:07:31Это означает, что он имеет доступ
00:07:32к области кэша базового репозитория
00:07:33и его токену GitHub.
00:07:35Так что этот рабочий процесс
00:07:35выполнил checkout PR,
00:07:36установил его зависимости,
00:07:38и запустил сборку бенчмарка.
00:07:39Проблема, однако, в том,
00:07:40что этот форк содержал
00:07:40вредоносный код.
00:07:41В этом случае
00:07:42это был скрипт настройки V,
00:07:43который отравил
00:07:44магазин пакетов PMPM
00:07:45под тем же самым ключом кэша,
00:07:47который позже будет использовать
00:07:48рабочий процесс релиза.
00:07:49Они на самом деле предварительно вычислили
00:07:50это из публичного
00:07:51лок-файла PMPM
00:07:52используя ту же самую формулу,
00:07:54которую также использует рабочий процесс.
00:07:56Как только этот отравленный кэш
00:07:57был сохранен,
00:07:57они фактически сбросили
00:07:58эту ветку обратно,
00:07:59чтобы она соответствовала текущей
00:07:59основной ветке main,
00:08:00так что видимый PR
00:08:01выглядел как нулевой файл,
00:08:02no-op,
00:08:03и затем они закрыли этот PR
00:08:04и удалили
00:08:05вредоносную ветку.
00:08:06Так что со стороны
00:08:07выглядит так,
00:08:07как будто абсолютно ничего
00:08:08не произошло,
00:08:09но они отравили
00:08:10кэш того GitHub action.
00:08:11Это означает, что
00:08:12восемь часов спустя,
00:08:13когда обычный мейнтейнер
00:08:14слил несвязанный PR
00:08:15в main,
00:08:16это запустило релизный рабочий процесс
00:08:17TanStack,
00:08:18который восстановил
00:08:19отравленный кэш PMPM,
00:08:20и теперь код, контролируемый злоумышленником,
00:08:22запускался внутри
00:08:23этого релизного действия.
00:08:24Он затем использовал ту же логику
00:08:25с OIDC,
00:08:26чтобы получить токен для публикации в NPM,
00:08:28и сумел опубликовать
00:08:2984 версии самого себя
00:08:30в 42 пакетах TanStack,
00:08:32и ему даже не нужно было
00:08:33добираться до этапа
00:08:34публикации пакета
00:08:35этого действия.
00:08:36Забавно,
00:08:36но действие фактически провалилось,
00:08:37потому что некоторые тесты не прошли,
00:08:39так что он никогда не достиг этого этапа,
00:08:40но вредоносный код сработал
00:08:41и опубликовал их все
00:08:43независимо от этого.
00:08:43Так что злоумышленник сумел
00:08:44связать три границы доверия.
00:08:46Во-первых,
00:08:47код PR форка
00:08:47смог отравить
00:08:48кэш базового репозитория,
00:08:49затем кэш базового репозитория
00:08:51был восстановлен внутри
00:08:52реального рабочего процесса релиза,
00:08:53затем настоящий рабочий процесс релиза
00:08:54имеет разрешения OIDC,
00:08:56которые превращаются в
00:08:57доступ к публикации в NPM,
00:08:58так что они могут опубликовать
00:08:59то, что выглядит
00:08:59как полностью легитимные пакеты.
00:09:01И это то, что, я думаю,
00:09:02становится действительно пугающим
00:09:03в атаках на цепочку поставок.
00:09:05Они переходят от
00:09:05кражи токена
00:09:06одного мейнтейнера
00:09:07к злоупотреблению
00:09:08всей CI/CD системой в целом,
00:09:10а это означает,
00:09:11что все наши сигналы доверия
00:09:12начинают работать
00:09:13на злоумышленника.
00:09:14Это был подписанный пакет
00:09:15с действительной проверкой происхождения,
00:09:16опубликованный реальным рабочим процессом.
00:09:18Так что вот,
00:09:19это ShaiHalud4,
00:09:20и если вы хотите проверить,
00:09:21были ли вы скомпрометированы
00:09:21каким-либо из этих пакетов,
00:09:23я оставлю ссылки
00:09:23на посты в блогах ниже,
00:09:25которые расскажут,
00:09:25как вы можете узнать
00:09:26и что вы можете сделать,
00:09:27если вы установили
00:09:28один из них.
00:09:29Дайте знать в комментариях,
00:09:30что вы думаете
00:09:30об этом всём
00:09:31и об экосистеме NPM,
00:09:33пока вы там,
00:09:33подпишитесь,
00:09:34и, как всегда,
00:09:34увидимся в следующем видео.

Key Takeaway

Атака на цепочку поставок Shai-Hulud скомпрометировала 42 пакета TanStack путем отравления кэша GitHub Actions, что позволило злоумышленникам использовать OIDC-токены для легитимной публикации вредоносных версий.

Highlights

  • Вредоносная кампания Shai-Hulud скомпрометировала 160 пакетов, включая TanStack, UiPath и Mistral.

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

  • Вредонос внедряет себя в хуки Claude Code и планировщик задач VS Code для обеспечения длительного присутствия в системе.

  • ПО крадет учетные данные GitHub, AWS, Kubernetes, HashiCorp Vault, а также локальные файлы мессенджеров и SSH-ключи.

  • Механизм «кнопки мертвеца» удаляет все файлы в домашнем каталоге, если обнаружено удаление украденных токенов.

  • Атака на TanStack использовала отравление кэша GitHub Actions через pull_request_target для получения прав на публикацию через OIDC.

Timeline

Масштаб атаки и механизм внедрения

  • Атака затронула 160 пакетов в экосистемах NPM и PyPy.
  • Вредонос использует скрытые файлы и опциональные зависимости для выполнения нагрузки.
  • Скрипт установки отсоединяется от родительского процесса для скрытия следов в логах.

Вредоносное ПО использует файл routerinit.js и опциональные зависимости, которые указывают на «осиротевшие» коммиты в форках на GitHub. Это позволяет обходить проверки целостности. Скрипт жизненного цикла prepare принудительно завершается с кодом 1, чтобы скрыть факт выполнения вредоносного кода, а основной процесс переходит в фоновый режим.

Кража данных и деструктивные функции

  • Вредонос закрепляется в Claude Code и VS Code для перезапуска при каждом использовании.
  • ПО извлекает секреты из памяти GitHub Actions, метаданных AWS и кластеров Kubernetes.
  • Системная служба GitHub Token Monitor ежеминутно проверяет валидность токенов.
  • При попытке отзыва токенов запускается команда деструктивного удаления файлов (rm -rf).

Атакующие собирают максимально широкий спектр данных: от SSH-ключей и истории командной строки до учетных данных облачных провайдеров и содержимого HashiCorp Vault. Встроенная деструктивная логика срабатывает при попытке мейнтейнера восстановить контроль над токенами, уничтожая данные на машине пользователя.

Самораспространение и вектор атаки на CI/CD

  • Вредонос использует OIDC-токены для публикации пакетов от имени легитимного издателя.
  • TanStack был заражен через отравление кэша в рабочем процессе GitHub Actions.
  • Атакующие использовали pull_request_target для доступа к контексту безопасности базового репозитория.

Злоумышленники создали форк TanStack и добавили вредоносный коммит, который отравил кэш менеджера пакетов pnpm. Через 8 часов, при слиянии легитимного PR, рабочий процесс релиза восстановил этот кэш и запустил вредоносный код. Это позволило опубликовать 84 зараженные версии 42 пакетов, используя права OIDC для автоматической публикации без кражи постоянных токенов.

Community Posts

No posts yet. Be the first to write about this video!

Write about this video