С тех пор как я начал учиться программировать, я восхищаюсь уровнем доверия, который мы вкладываем в простую команду, подобную этой:

pip install package_name

В некоторых языках программирования, таких как Python, это простой, более или менее официальный способ установки зависимостей для ваших проектов. Такие установщики обычно привязаны к публичным репозиториям кода, куда каждый может свободно загружать пакеты кода для использования другими пользователями.

Вы, наверное, уже слышали о таких инструментах — у Node есть менеджер npm и реестр npm, система управления пакетами pip языка Python использует PyPI (Python Package Index), а систему gems для языка Ruby можно найти… на сайте RubyGems.

Загрузив и установив пакет из любого из этих источников, вы, по сути, доверяете его издателю, запуская соответствующий код на своём компьютере. Так может ли эта слепая вера быть использована злоумышленниками?


Конечно, может.

Ни одна из служб размещения пакетов не может гарантировать, что весь код, загружаемый её пользователями, не содержит вредоносных программ. Прошлое исследование показало, что typosquatting — атака с использованием версий имён популярных пакетов с опечатками — может быть невероятно эффективной при получении доступа к случайным компьютерам по всему миру.

Другие хорошо известные пути атаки цепочек зависимостей включают использование различных методов компрометации существующих пакетов или загрузку вредоносного кода под именами зависимостей, которых больше не существует.

Идея

Пытаясь взломать PayPal вместе со мной летом 2020 года, Джастин Гарднер поделился интересным фрагментом исходного кода Node.js, найденного в GitHub.

Код предназначался для внутреннего использования в PayPal, и в его файле package.json, по-видимому, содержалась смесь публичных и частных зависимостей — публичные пакеты от npm, а также имена непубличных пакетов, скорее всего, размещённых внутри PayPal. В то время этих имён не было в публичном реестре npm.

С логикой, диктующей, какой пакет должен быть получен из неясного здесь источника, возникло несколько вопросов:

  • Что произойдёт при загрузке в npm вредоносного кода с этими именами? Возможно ли, что некоторые внутренние проекты PayPal начнут по умолчанию переходить на новые публичные пакеты вместо частных?

  • Начнут ли разработчики или даже автоматизированные системы выполнять код внутри библиотек?

  • Если это сработает, сможем ли мы получить за это вознаграждение?

  • Сработает ли такая атака и против других компаний?

Без лишних церемоний я стал разрабатывать план, чтобы ответить на эти вопросы.

Идея заключалась в загрузке собственных «вредоносных» пакетов Node в реестр npm под всеми невостребованными именами, которые «будут звонить домой» с каждого компьютера, на котором они были установлены. Если какой-либо из пакетов в итоге устанавливается на серверах, принадлежащих PayPal, или в любом другом месте, то содержащийся в них код немедленно уведомляет меня.

На данном этапе я считаю важным пояснить, что каждая отдельная организация, которая служила целью во время этого исследования, предоставила разрешение на проверку своей безопасности либо в рамках общедоступных программ вознаграждения за нахождение ошибок, либо посредством частных соглашений. Пожалуйста, не пытайтесь проводить такой тест без разрешения.

«Это всегда DNS»

К счастью, npm позволяет при установке пакета автоматически выполнять произвольный код, что облегчает создание пакета Node, который собирает некоторую базовую информацию о каждом компьютере, на котором он установлен, с помощью своего сценария preinstall.

Чтобы найти баланс между возможностью идентифицировать организацию на основе данных и необходимостью избегать сбора слишком большого количества конфиденциальной информации, я остановился только на регистрации имени пользователя, имени хоста и текущего пути каждой уникальной установки. Наряду с внешними IP-адресами этого было достаточно, чтобы помочь специалистам по безопасности выявить потенциально уязвимые системы на основе моих отчётов, избегая при этом того, чтобы моё тестирование было ошибочно принято за реальную атаку.

Остался только вопрос: как мне вернуть эти данные?

Зная, что большинство возможных целей находится глубоко внутри хорошо защищённых корпоративных сетей, я решил, что кража данных DNS — это правильный путь.

Отправка информации моему серверу по протоколу DNS не была важна для работы самого теста, но она указывала на меньшую вероятность блокировки трафика и его обнаружения на выходе.Данные в шестнадцатеричной кодировке использовались как часть DNS-запроса, который достигал моего собственного полномочного сервера доменных имён напрямую или через промежуточные сопоставители. Сервер был настроен для регистрации каждого полученного запроса, по сути, сохраняя запись каждого компьютера, в который были загружены пакеты.

Чем больше, тем лучше

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

Первая стратегия заключалась в поиске альтернативных экосистем для атаки. Поэтому я перенёс код как на Python, так и на Ruby, чтобы можно было загружать аналогичные пакеты в PyPI (Python Package Index) и RubyGems соответственно.

Однако, возможно, самой важной частью этого теста был поиск максимально возможного количества значимых имён зависимостей.

Несколько полных дней поиска имён частных пакетов, принадлежащих некоторым целевым компаниям, показали, что многие другие имена можно найти в GitHub, а также в основных службах размещения пакетов — во внутренних пакетах, которые были случайно опубликованы, — и даже в сообщениях на различных интернет-форумах.

Однако, оказалось, что лучше всего искать имена частных пакетов... в файлах JavaScript.

Видимо, довольно часто внутренние файлы package.json с именами зависимостей проектов JavaScript, встраиваются в общедоступные файлы сценариев во время их сборки, раскрывая имена внутренних пакетов. Точно так же получаемые по каналам утечки внутренние пути или вызовы require() в таких файлах также могут содержать имена зависимостей. Apple, Yelp и Tesla — это лишь несколько примеров компаний, у которых внутренние имена были раскрыты таким образом.

Во второй половине 2020 года, благодаря помощи пользователя @streaak и его замечательным навыкам реконструкции, мы смогли автоматически просканировать миллионы доменов, принадлежащих целевым компаниям, и извлечь сотни дополнительных имён пакетов JavaScript, которые ещё не были заявлены в реестре npm.Затем я загрузил свой код в службу размещения пакетов под всеми найденными именами и стал ждать обратных вызовов.

Результаты

Успех был просто поразительным.

От единичных ошибок, допущенных разработчиками на собственных компьютерах, до неправильно сконфигурированных внутренних или облачных серверов сборки, до системно уязвимых конвейеров разработки, было ясно одно: использование действительных имён внутренних пакетов было способом, который почти наверняка позволял проникнуть в сети некоторых крупнейших технологических компаний, получить доступ к удалённому выполнению кода и, возможно, позволить злоумышленникам добавлять бэкдоры во время сборки.

На тот момент уязвимость этого типа, которую я начал называть путаницей зависимостей, была обнаружен более чем в 35 организациях, во всех трёх тестируемых языках программирования. У подавляющего большинства затронутых компаний больше 1000 сотрудников, что, скорее всего, отражает более высокую распространённость внутреннего использования библиотек в более крупных организациях.

Поскольку имена зависимостей JavaScript легче найти, почти 75 % всех зарегистрированных обратных вызовов исходили из пакетов npm, но это не обязательно означает, что Python и Ruby менее восприимчивы к такой атаке. На самом деле, несмотря на то что во время моих поисков мне удалось идентифицировать лишь внутренние имена gem для Ruby, принадлежащие восьми организациям, четыре из этих компаний оказались уязвимыми для путаницы зависимостей посредством RubyGems.

Одна такая компания — канадский гигант электронной коммерции Giant Shopify, система сборки которой автоматически устанавливала пакет gem для Ruby с именем shopify-cloud всего лишь через несколько часов после того, как я его загрузил, а затем попытался выполнить код внутри него. Специалисты Shopify подготовили исправление в течение дня, а за обнаружение ошибки была присуждена награда в размере 30 000 долларов.

Ещё одна награда в размере 30 000 долларов была получена от Apple после того, как код в пакете Node, который я загрузил в npm в августе 2020 года, был выполнен на нескольких компьютерах внутри сети этой компании. Затронутые проекты оказались связаны с системой аутентификации Apple, известной за пределами компании как Apple ID.

Когда я подал идею о том, что эта ошибка, возможно, позволяла злоумышленникам внедрять бэкдоры в Apple ID, в компании Apple не посчитали, что этот уровень воздействия точно отражает проблему, и заявили:

Реализация бэкдора в оперативной службе требует более сложной последовательности событий, и это очень специфический термин, который несёт в себе дополнительные коннотации.

Однако в компании Apple подтвердили возможность удалённого выполнения кода на серверах Apple с помощью этой техники пакетов npm. Судя по потоку установок пакетов, проблема была исправлена в течение двух недель после моего отчёта, но премия за обнаружение ошибки была присуждена менее чем за день до этой публикации.

Такое же использование пакетов npm, устанавливаемых как на внутренние серверы, так и на ПК отдельных разработчиков, наблюдалось и в нескольких других успешных атаках на другие компании, причём некоторые установки часто выполнялись через часы или даже минуты после загрузки пакетов.

О, а имена PayPal, с которых всё началось? Они тоже сработали, что привело к ещё одной награде в размере 30 000 долларов. Фактически размер большинства присужденных премий за обнаружение ошибки был максимально разрешённым в рамках политики каждой программы, а иногда и превышал максимум, подтверждая в целом высокую серьезность уязвимости «путаница зависимостей».

К затронутым компаниям также относятся Netflix, Yelp и Uber.

«Это не баг, это фича»

Несмотря на получение большого количества результатов по уязвимости «путаница зависимости», одна деталь была — и в определённой степени остаётся до сих пор — неясной: почему это происходит? Каковы основные первопричины уязвимости такого рода?

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

Например, главный виновник путаницы зависимостей в Python, — это, по-видимому,  неправильное использование «небезопасного по своей конструкции» аргумента командной строки --extra-index-url. Если, используя этот аргумент с командой pip install library, указать собственный индекс пакета, можно обнаружить, что он работает ожидаемым образом, но то, что pip на самом деле делает за кулисами, выглядит примерно так:

  • проверяет существование library в указанном (внутреннем) индексе пакетов;

  • проверяет существование library в публичном индексе пакетов (PyPI);

  • устанавливает любую найденную версию. Если пакет существует в обоих индексах, то по умолчанию он устанавливается из источника с более высоким номером версии.

Поэтому загрузка пакета с именем library 9000.0.0 в PyPI приведёт к захвату зависимости в приведённом выше примере.

Хотя такое поведение уже было широко известно, простого поиска в GitHub выражения --extra-index-url было достаточно, чтобы найти несколько уязвимых сценариев, принадлежащих крупным организациям, включая ошибку, влияющую на компонент Microsoft .NET Core. Данная уязвимость, которая, возможно, позволила бы добавлять бэкдоры в .NET Core, к сожалению, была обнаружена вне рамок программы вознаграждения за нахождение ошибок в .NET.

В Ruby команда gem install --source работает аналогичным образом, но мне не удалось подтвердить, было ли её использование основной причиной каких-либо моих находок.

Конечно, замена --extra-index-url на --index-url быстро и прямо исправляет уязвимость, но снизить риск некоторых других вариантов уязвимости «путаница зависимостей» оказалось гораздо труднее.

JFrog Artifactory, часть программного обеспечения, широко используемая для размещения внутренних пакетов всех типов, предлагает возможность смешивать внутренние и публичные библиотеки в одном «виртуальном» репозитории, значительно упрощая управление зависимостями. Однако несколько клиентов заявили, что в Artifactory используется точно такой же уязвимый алгоритм, что был описан выше, при выборе между обслуживанием внутреннего и внешнего пакета с одним именем. На момент написания статьи не было никакого способа изменить это поведение по умолчанию.

В JFrog, как сообщается, знают об этой проблеме, но рассматривает её возможное исправление как «запрос функции» без расчётного времени его реализации, в то время как некоторые клиенты компании прибегли к изменению системной политики управления зависимостями, чтобы уменьшить риск путаницы зависимостей.

Компания Microsoft также предлагает аналогичную службу размещения пакетов под названием Azure Artifacts. По результатам одного из моих отчётов в эту службу были внесены некоторые незначительные улучшения, чтобы она могла гарантированно предоставить надёжный путь обхода уязвимостей «путаница зависимостей». Как ни странно, эта проблема была обнаружена не в результате тестирования самой службы Azure Artifacts, а, скорее, путём успешной атаки на облачную систему Microsoft Office 365, в результате чего за этот отчёт была получена самая высокая награда Azure в размере 40 000 долларов.

Более подробную информацию о первопричинах и рекомендациях по профилактике можно найти в техническом документе Microsoft 3 Ways to Mitigate Risk When Using Private Package Feeds.

Будущие исследования?

Хотя многие крупные технологические компании уже осведомлены об уязвимости этого типа и либо исправили её в своей инфраструктуре, либо работают над внедрением мер по смягчению последствий, у меня сохраняется ощущение, что есть ещё что-то, что нужно обнаружить.

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

Тем не менее я искренне призываю вас, независимо от вашего уровня опыта, потратить некоторое время и проработать эту идею в глубине своего сознания, независимо от того, связана она с безопасностью управления зависимостями или нет.

Даже крупные компании совершают порой ошибки и допускают уязвимости. На нашем курсе «Этичный хакер» мы учим студентов искать эти уязвимости и зарабатывать на этом. Если устали чинить баги в коде и постоянно надстраивать что-то новое — приходите к нам, будем учиться «ломать», находя бреши в софте даже у крутых корпораций.

Узнайте, как прокачаться в других специальностях или освоить их с нуля:

Другие профессии и курсы