Задумывались ли вы о том, что находится внутри зависимостей, которые так или иначе подтягиваются в ваш код? Взять чужую библиотеку сейчас — норма жизни, но чем это обернется с точки зрения безопасности?
![](https://habrastorage.org/getpro/habr/upload_files/12c/399/0a4/12c3990a4880cee8e3e10f47fc93efb4.png)
Последние истории с node‑ipc и CTX заставили задуматься о том, что лежит внутри этих репозиториев. Оказалось, не только легитимный код. Там есть и попытки заработать без особых усилий, просто собирая информацию, и даже полноценные стиллеры. Причем негативных изменений стало больше после известных событий.
Почему мы обратили внимание на зависимости
Меня зовут Леонид Безвершенко, я — младший исследователь угроз информационной безопасности в команде Глобального центра исследований и анализа угроз (GReAT) подразделения Threat Research «Лаборатории Касперского». Это исследование, а также наш инструмент для поиска закладок в репозиториях и, конечно, сама статья написаны в соавторстве с моим коллегой по GReAT, главным экспертом по исследованию угроз информационной безопасности Игорем Кузнецовым.
В марте 2022 года мы с Игорем решили отвлечься от привычного исследования бинарщины — рассмотрения apt, реверса malware — и заглянуть в opensource. В этом направлении нас подтолкнула новость о node-ipc, который используется в большом количестве других пакетов (в том числе в Unity Hub) в качестве зависимости.
17 марта появилась информация, что пакет удаляет файлы у пользователей, IP которых бьется как русский или белорусский.
Когда в проблеме начали разбираться и в конечном счете обратились к автору, тот сначала отмалчивался, а потом заявил, что это его способ выразить протест. Иными словами, это не история взлома, а пример того, как автор сам злонамеренно превратил свой пакет в троян.
![](https://habrastorage.org/getpro/habr/upload_files/770/46a/f7a/77046af7a2734a0da713020a7797e4d0.png)
Мы предположили, что подобные истории наверняка можно найти и в других 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 он открывает у пользователя порносайт.
![](https://habrastorage.org/getpro/habr/upload_files/1ec/f76/f3b/1ecf76f3b47d0b50d87271004089081f.png)
Спустя некоторое время по секьюрити комьюнити прошла еще одна история о Rust-пакете rustdecimal. Пакет скопировали с опечаткой в имени, добавив в новую версию malware. Автор оригинального пакета был в курсе, но сообщество не знает, как работать с такими атаками.
![](https://habrastorage.org/getpro/habr/upload_files/908/f1c/713/908f1c713cf5b7e933859f9ae85ef25a.png)
Так мы пришли к идее проверки opensource-пакетов, которые часто подтягивают в качестве зависимостей.
Там должно быть что-то…
Идея была довольно простая. Надо собрать все пакеты, изменившиеся с февраля 2022 года (потому что это горячее время), посмотреть разницу с предыдущей версией и проанализировать в поисках закладок. Изначально было понятно, что правила детектирования придется исправлять, поэтому обнаруженные изменения мы решили складывать в базу, чтобы потом их можно было легко пересканировать.
Под эту идею мы расчехлили свою заначку — виртуальную машину с 4 ТБ дискового пространства:
4 ТБ HDD
16 ГБ RAM
Ubuntu
Python & PostgreSQL
Мы решили начать с двух репозиториев: npm (node.js) и PyPI (python) — они довольно популярные, да и в новостях уже появлялась информация о находках.
Обобщенная архитектура написанного нами сервиса выглядит так:
![](https://habrastorage.org/getpro/habr/upload_files/84c/85f/fdf/84c85ffdf4259a1c1aaab0dd9034593b.png)
Четыре модуля работают асинхронно:
Первый взаимодействует с API репозиториев (npm или PyPI в нашем случае). Он берет данные о новых релизах пакетов — сверяет не только сами версии, но и описание, ищет новых контрибьюторов и т. п.
Второй скачивает новый релиз.
Третий сравнивает новую и предыдущую версии пакетов. Это классический инструмент Diff. Назовем его Differ.
Четвертый — сканер. Мы подобрали набор хантинговых правил, который нам действительно помог.
Но не все так просто…
Прежде чем рассказывать о находках, остановимся на сложностях, с которыми пришлось столкнуться.
Первая проблема заключалась в том, что, когда мы собрали весь этот асинхронный сетап, он стал работать слишком быстро. Нас начали банить.
![](https://habrastorage.org/getpro/habr/upload_files/015/d09/6e8/015d096e839151cede9ee7943794add7.png)
Пришлось различными эвристическими способами подбирать sleep.
![](https://habrastorage.org/getpro/habr/upload_files/945/0fd/f40/9450fdf401193f6433e20f60ebbf8fd2.png)
Мы с этим справились и даже не сильно замедлились. Вышли на поток. Задача была — получать обновления первыми, чтобы что-то зарепортить до того, как пакет скачают другие пользователи.
Следующая проблема скрывалась в Differ-е. На одном из пакетов размером не более 900 МБ он завис на несколько часов. А ведь это был не самый большой размер пакета — встречаются файлы по 2 или 3 ГБ. Мы прогнали на этом пакете обычный Diff из Linux, и тот отработал за несколько секунд.
Мы предположили, что проблема в нашем коде и его надо дебажить. Но после всех изысканий оказалось, что виновата стандартная библиотека Python — difflib, которая сравнивает файлы и прочие объекты. Оказывается, она не оптимизирована и для нашей задачи не подходит — с ней мы просто не смогли бы выйти на поток.
![](https://habrastorage.org/getpro/habr/upload_files/07e/29f/a51/07e29fa51d17e802eef1bccb665c4e4e.png)
![](https://habrastorage.org/getpro/habr/upload_files/c76/fa2/0f7/c76fa20f7c8c655810b65b002fac1e4f.png)
В качестве альтернативы мы нашли diff-match-patch от Google. Сами ее не тестировали, но поверили комментатору со Stackoverflow, который заявлял, что она в 20 раз быстрее.
![](https://habrastorage.org/getpro/habr/upload_files/0e9/f92/f9d/0e9f92f9d20462cfc07f45ef94a1a83f.png)
С этой библиотекой все заработало очень хорошо. Мы немного потеряли в красоте Differ-а, но в данном случае она была не важна.
Следующая проблема, с которой мы встретились, также была связана с Differ-ом.
Мы увидели, что наш робот поднимается и падает из-за того, что не хватает оперативной памяти. Размер пакета — 186 МБ, что странно. Но когда мы его разархивировали, там оказалось 3 ГБ. Пакет назывался ‘similar-persian-words’ и, как ни странно, содержал в себе JSON со всеми персидскими словами. Им действительно пользуются, у него есть загрузки.
![](https://habrastorage.org/getpro/habr/upload_files/a85/874/8f3/a858748f3bf6ec11d4fe02368d5d9fce.png)
Понятно, что здесь была проблема арифметики. У нас было 16 ГБ оперативной памяти, а размер пакета — 3 ГБ. Итого: 3 ГБ — предыдущая версия, 3 ГБ — новая версия и еще сам Differ требовал 3 ГБ. После этого анализа мы нашли и исправили ошибку в своем коде — он не очень правильно считал хеш от разницы (не чанками). Естественно, пришлось и объем памяти на виртуальной машине увеличить.
Что мы обнаружили
А теперь поговорим про найденные проблемы.
Всего за 5 месяцев, с февраля 2022 года, в двух репозиториях мы проанализировали 2 184 498 пакетов. Из них:
97% — npm
3% — PyPI
По графику ниже видно, что разработчики open source по выходным отдыхают. А разработчики PyPI на фоне разработчиков npm отдыхают почти всегда.
![](https://habrastorage.org/getpro/habr/upload_files/eec/e30/7d5/eece307d57efcb62e3491374cc1a059a.png)
Еще немного статистики:
мы скачали 4 873 242 уникальные версии пакетов;
в хранилище в сжатом виде пакеты вместе с изменениями занимают 6 ТБ.
Мы храним в том числе и удаленные пакеты, которые более недоступны. Когда в новостях мы слышим о том, что в очередном пакете найдена какая-то проблема, как правило, он уже удален — не получается его подробно изучить после того, как кто-то из вендоров в нем что-то обнаружил. Но благодаря тому что мы храним все, мы можем ретроспективно писать новые правила и придумывать свежие эвристики.
Наш набор правил (в том числе на обфускацию) обнаружил 6200 подозрительных изменений. Это примерно 0,1% от всех проанализированных расхождений. Сюда включены все выявленные в пакетах политические лозунги, проявления активизма, удаление файлов и т. п.
![](https://habrastorage.org/getpro/habr/upload_files/c8e/305/8f6/c8e3058f6ea8a2dc1e09f6c592ebf048.png)
Мы посмотрели вглубь и нашли довольно странные вещи.
Например, нашли npm-пакеты, в которых есть куски легитимного кода, но прямо в инсталляции внедрен сбор информации о системе, пользователе, переменных окружения и т. п. Больше ничего этот код не делает.
![](https://habrastorage.org/getpro/habr/upload_files/81e/78c/7e2/81e78c7e2c92f721410edad5845b09ae.png)
Выглядит подозрительно. Все бы ничего, если бы не домены, куда это все отсылается, — interactsh, burpcollaborator или pydream. Возможно, читателю они знакомы. Это бесплатные эндпоинты для разработчиков и тестеров.
Есть и более подозрительные вещи, в частности скрипты конкретно под Windows, которые собирают еще больше информации, вплоть до наличия административных прав у пользователя.
![](https://habrastorage.org/getpro/habr/upload_files/150/e40/0ff/150e400ff3ba2c44cf5f266fdc7e4095.png)
Этот скрипт тоже отсылает всю информацию, которую только можно запаковать, но никакого дополнительного кода не исполняет. Понятно, что следующая версия могла прийти уже с полной нагрузкой. Но тем не менее чаще мы видели только такие «отстуки».
В один прекрасный день мы нашли пакет, в котором в readme автор честно описал, что же происходит. Он просто пытается получить денег с какого-нибудь вендора.
![](https://habrastorage.org/getpro/habr/upload_files/87a/928/dfd/87a928dfd15d59e33b0404adb6721091.png)
В качестве объяснения своего поступка он даже приложил ссылку, где за подобное заплатили 3 тысячи долларов.
![](https://habrastorage.org/getpro/habr/upload_files/dc8/836/1be/dc88361be535572ad09a847c6b970f5a.png)
Помимо любителей такого заработка мы нашли огромное количество пакетов — 1915 штук — которые так или иначе обфусцированы. В основном это obfuscate.io.
![](https://habrastorage.org/getpro/habr/upload_files/230/790/e95/230790e958181510ebf7c2c41474ddff.png)
Чтобы понять, что внутри, нужно каждый из них деобфусцировать. Иногда люди закрывают свои пакеты, думая, что это спасет от выуживания оттуда некой информации. Но выглядит это так же подозрительно, как и malware. Все это нужно разбирать.
После фильтрации осталось 638 пакетов от горе-хакеров.
![](https://habrastorage.org/getpro/habr/upload_files/b9f/468/cd2/b9f468cd280a5b3a6bb3db2ea9a1325c.png)
Среди них есть и вполне «безобидные» истории, которые легко отлавливаются одним из правил. Например, часто эти горе-хакеры используют бесплатные хостинги для питон-скриптов, чтобы реализовать админку, собрать информацию и пойти с ней к вендору.
Но дальше появились более интересные находки.
Больше всего (60+) мы нашли программ, которые воруют кредиты Discord. Понятно, что до кучи они собирают информацию по кредитным картам, троянят сам Discord. Но почему-то пользователи этого мессенджера — основная таргет-аудитория.
![](https://habrastorage.org/getpro/habr/upload_files/ccb/938/9cb/ccb9389cb2e8712fa40ff243f8542335.png)
Еще одна интересная находка была в удаленных пакетах.
Как правило, в новостях об обнаруженном malware публикуется целый список пакетов, подробно в которые никто не смотрит. Мы наткнулись на такой python-пакет aiohttp-proxies-forked, благо у нас он был уже отложен к моменту выхода публикации (и удаления пакета). Внутри мы обнаружили прекрасный асинхронный бэкдор под Windows, причем завернутый не в exe (https://blog.sonatype.com/this-week-in-malware-npm-malware-exfiltrates-windows-sam-amazon-ec2-credentials).
![](https://habrastorage.org/getpro/habr/upload_files/b18/1d6/29e/b181d629e5b405835c6da9cd0eab6d7c.png)
![](https://habrastorage.org/getpro/habr/upload_files/c3c/1c4/81a/c3c1c481aefe44f89a97e344dcaf5cc4.png)
Он был написан на Python.
![](https://habrastorage.org/getpro/habr/upload_files/d10/76b/368/d1076b3681e84e4ac33c97d6329ed809.png)
Логирование в этом пакете на чистейшем русском.
![](https://habrastorage.org/getpro/habr/upload_files/29c/0f8/759/29c0f875956d84537ecf64d9f9fb60fd.png)
Более свежая находка — и, пожалуй, одна из самых интересных — пакет, загружающий полноценный стиллер.
Прочитав очередной отчет Checkmarx с большим количеством вредоносных пакетов, мы обновили свои правила и просканировали все, что было обнаружено к этому моменту. И таким образом наткнулись на два пакета, которые были гораздо интереснее всего списка Checkmarx, — ultrarequests и pyrequests. Они мимикрируют под один из самых известных пакетов — requests.
Фокус заключался в том, что злоумышленник полностью скопировал описание легитимного пакета, так что на сайте PyPi мы видим оригинальную статистику — 48 тысяч звезд GitHub, 8 тысяч форков, 230 миллионов скачиваний. Увидев эту статистику, человек подумает, что ничего плохого в пакете быть не может.
![](https://habrastorage.org/getpro/habr/upload_files/d00/754/9bf/d007549bf4e19c8b820fd923080c8b24.png)
Внутри этот пакет почти ничем не отличается от оригинального requests, кроме одного файла — exceptions.py. В нем в одном из классов ошибок сделан злой прелоад в base64, который декодируется и загружает стиллер. Он, в свою очередь, ворует данные кредитных карт, ищет файлы с паролями, ворует cookie и сохраненные пароли и т. п.
Мы сразу же сообщили об этом пакете в PyPi, но быстрого ответа не получили. Зато когда написали твит, он разлетелся очень быстро (https://twitter.com/bzvr_/status/1557807795877171201).
![](https://habrastorage.org/getpro/habr/upload_files/795/35c/242/79535c242d0ca4b35db9f4bd36e02aca.png)
После того как мы сообщили о пакете в базу открытых уязвимостей Snyk.io, его почти сразу забанили. А на PyPi пакет прожил две недели. Только потом нам ответили из ассоциации Python, что очень нам благодарны за находку, и удалили его.
Любопытно, что по графику скачиваний этого пакета после твита видно, что его популярность выросла почти на 50%. То есть мы в принципе его разрекламировали.
Что мы будем делать дальше
Наше исследование — лишь вершина айсберга. Мы попробовали текстовый diff, нашли некоторое количество закладок. Но мир open source огромен. Плюс есть еще много языков, которые мы не успели подключить, — для начала это Rust и Golang. И мы уже работаем над этим.
![](https://habrastorage.org/getpro/habr/upload_files/d79/234/997/d79234997b821230c572d01d2dc41434.png)
Следующая идея — следить за всем GitHub. И это бесконечная история. Нам каждый день прилетают какие-то детекты, с ними нужно что-то делать, но рук не хватает. Хочется сказать: «Горшочек не вари», но мы продолжаем. Уверен, что у нас еще будут интересные находки, и именно вы можете стать тем, кто их обнаружит. Присоединяйтесь к нашим командам Threat Research и командам Security Services. Сможете заниматься расследованием кибершпионажа, поиском глобального malware, программами-вымогателями и прочими трендами мира киберпреступлений. Это новые интереснейшие задачи каждый день.
Разрабатываете, но не знаете, как в этой ситуации жить дальше? Следите за нашими анонсами. В разработке для частных целей, наверное, этого достаточно. Не без влияния таких новостных публикаций вендоры начинают следить за репозиториями, и это радует. Корпоративной разработке сложнее — возможно, придется морозить версии, поскольку однозначного ответа на вопрос о безопасности нет.