NPM-червь вернулся, и стало еще хуже (взлом TanStack)
BBetter Stack
Computing/SoftwareManagementInternet Technology
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увидимся в следующем видео.
Community Posts
No posts yet. Be the first to write about this video!
Write about this video