Задумывались ли вы о том, что находится внутри зависимостей, которые так или иначе подтягиваются в ваш код? Взять чужую библиотеку сейчас — норма жизни, но чем это обернется с точки зрения безопасности?
Последние истории с node‑ipc и CTX заставили задуматься о том, что лежит внутри этих репозиториев. Оказалось, не только легитимный код. Там есть и попытки заработать без особых усилий, просто собирая информацию, и даже полноценные стиллеры. Причем негативных изменений стало больше после известных событий.
Почему мы обратили внимание на зависимости
Меня зовут Леонид Безвершенко, я — младший исследователь угроз информационной безопасности в команде Глобального центра исследований и анализа угроз (GReAT) подразделения Threat Research «Лаборатории Касперского». Это исследование, а также наш инструмент для поиска закладок в репозиториях и, конечно, сама статья написаны в соавторстве с моим коллегой по GReAT, главным экспертом по исследованию угроз информационной безопасности Игорем Кузнецовым.
В марте 2022 года мы с Игорем решили отвлечься от привычного исследования бинарщины — рассмотрения apt, реверса malware — и заглянуть в opensource. В этом направлении нас подтолкнула новость о node-ipc, который используется в большом количестве других пакетов (в том числе в Unity Hub) в качестве зависимости.
17 марта появилась информация, что пакет удаляет файлы у пользователей, IP которых бьется как русский или белорусский.
Когда в проблеме начали разбираться и в конечном счете обратились к автору, тот сначала отмалчивался, а потом заявил, что это его способ выразить протест. Иными словами, это не история взлома, а пример того, как автор сам злонамеренно превратил свой пакет в троян.
Мы предположили, что подобные истории наверняка можно найти и в других opensource-пакетах. Эта идея лежит на поверхности. Выполняя install, мы особо не смотрим, что скачиваем. А все распространенные современные языки поддерживают парадигму «скачай и запусти какую-нибудь зависимость».
-
Node: “node install%package%”
-
Python: “pip install%package%”
-
Rust: curl …rustup.sh | /bin/sh; cargo run
-
Go: go get github.com/… В этом случае пакет скачивается с GitHub и можно посмотреть на него до того, как он соберется. Но вряд ли кто‑то так делает…
Казалось бы, у популярных пакетов миллионы пользователей, централизованные репозитории. Кто-то ведь должен следить за безопасностью? Но мы заглянули внутрь пакетов и поняли, что это работает не так.
Например, в Python есть пакет, который должен заниматься математикой. Но с вероятностью 1 к 100 он открывает у пользователя порносайт.
Спустя некоторое время по секьюрити комьюнити прошла еще одна история о Rust-пакете rustdecimal. Пакет скопировали с опечаткой в имени, добавив в новую версию malware. Автор оригинального пакета был в курсе, но сообщество не знает, как работать с такими атаками.
Так мы пришли к идее проверки opensource-пакетов, которые часто подтягивают в качестве зависимостей.
Там должно быть что-то…
Идея была довольно простая. Надо собрать все пакеты, изменившиеся с февраля 2022 года (потому что это горячее время), посмотреть разницу с предыдущей версией и проанализировать в поисках закладок. Изначально было понятно, что правила детектирования придется исправлять, поэтому обнаруженные изменения мы решили складывать в базу, чтобы потом их можно было легко пересканировать.
Под эту идею мы расчехлили свою заначку — виртуальную машину с 4 ТБ дискового пространства:
-
4 ТБ HDD
-
16 ГБ RAM
-
Ubuntu
-
Python & PostgreSQL
Мы решили начать с двух репозиториев: npm (node.js) и PyPI (python) — они довольно популярные, да и в новостях уже появлялась информация о находках.
Обобщенная архитектура написанного нами сервиса выглядит так:
Четыре модуля работают асинхронно:
-
Первый взаимодействует с API репозиториев (npm или PyPI в нашем случае). Он берет данные о новых релизах пакетов — сверяет не только сами версии, но и описание, ищет новых контрибьюторов и т. п.
-
Второй скачивает новый релиз.
-
Третий сравнивает новую и предыдущую версии пакетов. Это классический инструмент Diff. Назовем его Differ.
-
Четвертый — сканер. Мы подобрали набор хантинговых правил, который нам действительно помог.
Но не все так просто…
Прежде чем рассказывать о находках, остановимся на сложностях, с которыми пришлось столкнуться.
Первая проблема заключалась в том, что, когда мы собрали весь этот асинхронный сетап, он стал работать слишком быстро. Нас начали банить.
Пришлось различными эвристическими способами подбирать sleep.
Мы с этим справились и даже не сильно замедлились. Вышли на поток. Задача была — получать обновления первыми, чтобы что-то зарепортить до того, как пакет скачают другие пользователи.
Следующая проблема скрывалась в Differ-е. На одном из пакетов размером не более 900 МБ он завис на несколько часов. А ведь это был не самый большой размер пакета — встречаются файлы по 2 или 3 ГБ. Мы прогнали на этом пакете обычный Diff из Linux, и тот отработал за несколько секунд.
Мы предположили, что проблема в нашем коде и его надо дебажить. Но после всех изысканий оказалось, что виновата стандартная библиотека Python — difflib, которая сравнивает файлы и прочие объекты. Оказывается, она не оптимизирована и для нашей задачи не подходит — с ней мы просто не смогли бы выйти на поток.
В качестве альтернативы мы нашли diff-match-patch от Google. Сами ее не тестировали, но поверили комментатору со Stackoverflow, который заявлял, что она в 20 раз быстрее.
С этой библиотекой все заработало очень хорошо. Мы немного потеряли в красоте Differ-а, но в данном случае она была не важна.
Следующая проблема, с которой мы встретились, также была связана с Differ-ом.
Мы увидели, что наш робот поднимается и падает из-за того, что не хватает оперативной памяти. Размер пакета — 186 МБ, что странно. Но когда мы его разархивировали, там оказалось 3 ГБ. Пакет назывался ‘similar-persian-words’ и, как ни странно, содержал в себе JSON со всеми персидскими словами. Им действительно пользуются, у него есть загрузки.
Понятно, что здесь была проблема арифметики. У нас было 16 ГБ оперативной памяти, а размер пакета — 3 ГБ. Итого: 3 ГБ — предыдущая версия, 3 ГБ — новая версия и еще сам Differ требовал 3 ГБ. После этого анализа мы нашли и исправили ошибку в своем коде — он не очень правильно считал хеш от разницы (не чанками). Естественно, пришлось и объем памяти на виртуальной машине увеличить.
Что мы обнаружили
А теперь поговорим про найденные проблемы.
Всего за 5 месяцев, с февраля 2022 года, в двух репозиториях мы проанализировали 2 184 498 пакетов. Из них:
-
97% — npm
-
3% — PyPI
По графику ниже видно, что разработчики open source по выходным отдыхают. А разработчики PyPI на фоне разработчиков npm отдыхают почти всегда.
Еще немного статистики:
-
мы скачали 4 873 242 уникальные версии пакетов;
-
в хранилище в сжатом виде пакеты вместе с изменениями занимают 6 ТБ.
Мы храним в том числе и удаленные пакеты, которые более недоступны. Когда в новостях мы слышим о том, что в очередном пакете найдена какая-то проблема, как правило, он уже удален — не получается его подробно изучить после того, как кто-то из вендоров в нем что-то обнаружил. Но благодаря тому что мы храним все, мы можем ретроспективно писать новые правила и придумывать свежие эвристики.
Наш набор правил (в том числе на обфускацию) обнаружил 6200 подозрительных изменений. Это примерно 0,1% от всех проанализированных расхождений. Сюда включены все выявленные в пакетах политические лозунги, проявления активизма, удаление файлов и т. п.
Мы посмотрели вглубь и нашли довольно странные вещи.
Например, нашли npm-пакеты, в которых есть куски легитимного кода, но прямо в инсталляции внедрен сбор информации о системе, пользователе, переменных окружения и т. п. Больше ничего этот код не делает.
Выглядит подозрительно. Все бы ничего, если бы не домены, куда это все отсылается, — interactsh, burpcollaborator или pydream. Возможно, читателю они знакомы. Это бесплатные эндпоинты для разработчиков и тестеров.
Есть и более подозрительные вещи, в частности скрипты конкретно под Windows, которые собирают еще больше информации, вплоть до наличия административных прав у пользователя.
Этот скрипт тоже отсылает всю информацию, которую только можно запаковать, но никакого дополнительного кода не исполняет. Понятно, что следующая версия могла прийти уже с полной нагрузкой. Но тем не менее чаще мы видели только такие «отстуки».
В один прекрасный день мы нашли пакет, в котором в readme автор честно описал, что же происходит. Он просто пытается получить денег с какого-нибудь вендора.
В качестве объяснения своего поступка он даже приложил ссылку, где за подобное заплатили 3 тысячи долларов.
Помимо любителей такого заработка мы нашли огромное количество пакетов — 1915 штук — которые так или иначе обфусцированы. В основном это obfuscate.io.
Чтобы понять, что внутри, нужно каждый из них деобфусцировать. Иногда люди закрывают свои пакеты, думая, что это спасет от выуживания оттуда некой информации. Но выглядит это так же подозрительно, как и malware. Все это нужно разбирать.
После фильтрации осталось 638 пакетов от горе-хакеров.
Среди них есть и вполне «безобидные» истории, которые легко отлавливаются одним из правил. Например, часто эти горе-хакеры используют бесплатные хостинги для питон-скриптов, чтобы реализовать админку, собрать информацию и пойти с ней к вендору.
Но дальше появились более интересные находки.
Больше всего (60+) мы нашли программ, которые воруют кредиты Discord. Понятно, что до кучи они собирают информацию по кредитным картам, троянят сам Discord. Но почему-то пользователи этого мессенджера — основная таргет-аудитория.
Еще одна интересная находка была в удаленных пакетах.
Как правило, в новостях об обнаруженном malware публикуется целый список пакетов, подробно в которые никто не смотрит. Мы наткнулись на такой python-пакет aiohttp-proxies-forked, благо у нас он был уже отложен к моменту выхода публикации (и удаления пакета). Внутри мы обнаружили прекрасный асинхронный бэкдор под Windows, причем завернутый не в exe (https://blog.sonatype.com/this-week-in-malware-npm-malware-exfiltrates-windows-sam-amazon-ec2-credentials).
Он был написан на Python.
Логирование в этом пакете на чистейшем русском.
Более свежая находка — и, пожалуй, одна из самых интересных — пакет, загружающий полноценный стиллер.
Прочитав очередной отчет Checkmarx с большим количеством вредоносных пакетов, мы обновили свои правила и просканировали все, что было обнаружено к этому моменту. И таким образом наткнулись на два пакета, которые были гораздо интереснее всего списка Checkmarx, — ultrarequests и pyrequests. Они мимикрируют под один из самых известных пакетов — requests.
Фокус заключался в том, что злоумышленник полностью скопировал описание легитимного пакета, так что на сайте PyPi мы видим оригинальную статистику — 48 тысяч звезд GitHub, 8 тысяч форков, 230 миллионов скачиваний. Увидев эту статистику, человек подумает, что ничего плохого в пакете быть не может.
Внутри этот пакет почти ничем не отличается от оригинального requests, кроме одного файла — exceptions.py. В нем в одном из классов ошибок сделан злой прелоад в base64, который декодируется и загружает стиллер. Он, в свою очередь, ворует данные кредитных карт, ищет файлы с паролями, ворует cookie и сохраненные пароли и т. п.
Мы сразу же сообщили об этом пакете в PyPi, но быстрого ответа не получили. Зато когда написали твит, он разлетелся очень быстро (https://twitter.com/bzvr_/status/1557807795877171201).
После того как мы сообщили о пакете в базу открытых уязвимостей Snyk.io, его почти сразу забанили. А на PyPi пакет прожил две недели. Только потом нам ответили из ассоциации Python, что очень нам благодарны за находку, и удалили его.
Любопытно, что по графику скачиваний этого пакета после твита видно, что его популярность выросла почти на 50%. То есть мы в принципе его разрекламировали.
Что мы будем делать дальше
Наше исследование — лишь вершина айсберга. Мы попробовали текстовый diff, нашли некоторое количество закладок. Но мир open source огромен. Плюс есть еще много языков, которые мы не успели подключить, — для начала это Rust и Golang. И мы уже работаем над этим.
Следующая идея — следить за всем GitHub. И это бесконечная история. Нам каждый день прилетают какие-то детекты, с ними нужно что-то делать, но рук не хватает. Хочется сказать: «Горшочек не вари», но мы продолжаем. Уверен, что у нас еще будут интересные находки, и именно вы можете стать тем, кто их обнаружит. Присоединяйтесь к нашим командам Threat Research и командам Security Services. Сможете заниматься расследованием кибершпионажа, поиском глобального malware, программами-вымогателями и прочими трендами мира киберпреступлений. Это новые интереснейшие задачи каждый день.
Разрабатываете, но не знаете, как в этой ситуации жить дальше? Следите за нашими анонсами. В разработке для частных целей, наверное, этого достаточно. Не без влияния таких новостных публикаций вендоры начинают следить за репозиториями, и это радует. Корпоративной разработке сложнее — возможно, придется морозить версии, поскольку однозначного ответа на вопрос о безопасности нет.
Автор: Леонид Безвершенко