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

Что такое путаница в зависимостях?

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

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

Термин путаница с зависимостями впервые был придуман исследователем безопасности Алексом Бирсаном в Феврале 2021 года в сообщении в блоге Medium. В своем посте он объявил, что провел подобную атаку против ряда крупных организаций и заработал значительную сумму денег на их программах по поиску ошибок.

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

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

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

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

Пример смешения с зависимостями

Часто организации разветвляются или отделяются и настраивают свою собственную версию библиотеки с открытым исходным кодом.

Представим вымышленную компанию Acme и фиктивную библиотеку Python с открытым исходным кодом под названием fetcher, которая используется для выполнения HTTP-запросов. Предположим, у Acme есть какая-то бизнес-потребность или технологическая потребность, которая требует от них отправки некоторых настраиваемых метаданных с каждым выполняемым HTTP-запросом. Для удобства они могут решить разветвить библиотеку сборщика и создать свою собственную версию с именем acme-fetcher, которая всегда обеспечивает их индивидуальное поведение.

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

Обычно их разрешение зависимостей будет выглядеть так:

  1. Пип делает запрос на загрузку acme-fetcher с версией больше или равной 1.2.3 и pyyaml ​​больше или равной 5.4.0.
  2. Внутренний репозиторий сверится с удаленным репозиторием (например, PyPI), чтобы узнать, есть ли в нем совпадения.
  3. PyPI вернет pyyaml ​​5.4.1, которая является самой большой версией, удовлетворяющей запросу.
  4. Внутренний репозиторий вернет acme-fetcher 1.2.3 и pyyaml ​​5.4.1 и, возможно, кеширует пакет pyyaml.

Если злоумышленник придет и узнает (или угадает) имя внутренних пакетов, используемых Acme, он может загрузить вредоносный пакет с тем же именем в PyPI. Если они загрузят его с более крупной версией, разрешение зависимости обычно будет выглядеть следующим образом:

  1. Пип делает запрос на загрузку acme-fetcher с версией больше или равной 1.2.3 и pyyaml ​​больше или равной 5.4.0.
  2. Внутренний репозиторий сверится с удаленным репозиторием (например, PyPI), чтобы узнать, есть ли в нем совпадения.
  3. PyPI вернет pyyaml ​​5.4.1, которая является самой большой версией, удовлетворяющей запросу, а также вредоносный acme-fetcher 999.0.0, который также удовлетворяет запрос.
  4. Внутренний репозиторий сравнит свою версию acme-fetcher с версией из публичного репозитория и вернет более подходящую версию. В этом случае возвращаются acme-fetcher 999.0.0 и pyyaml ​​5.4.1 и потенциально кэшируются оба пакета.

Защита от путаницы в зависимостях

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

  1. Удалите использование --extra-index-url аргумента pip.

Этот аргумент указывает дополнительные URL-адреса индексов пакетов для использования в дополнение к --index-url (по умолчанию PyPI). Если этот аргумент включен, это означает, что pip проверит оба репозитория на предмет соответствия пакета и будет использовать самую высокую обнаруженную применимую версию. Этот совет неявно предполагает, что использование --index-url и установка его для вашего внутреннего репозитория, по крайней мере, сузит вектор риска до вашего внутреннего репозитория.

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

2. Использование закрепления зависимостей

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

Стоит отметить, что закрепление зависимостей само по себе не защитит от атак путаницы зависимостей. Это связано с тем, что злоумышленник потенциально может опубликовать конфликтующую версию в общедоступном репозитории, и pip нужно будет угадать, следует ли выбрать внутреннюю версию или внешнюю версию.

3. Использовать хеширование зависимостей

Начиная с pip 8.0, pip теперь поддерживает хэши пакетов как часть спецификации зависимостей. Если для какой-либо зависимости указан --hash, он включит режим проверки хэша глобально, что означает, что pip будет проверять хэши для всех зависимостей. Когда включен режим проверки хэшей, он в значительной степени сводится к нулю, что означает, что у вас должны быть указаны хэши для всех зависимостей. Хеширование также требует закрепления версии.

Если вы хотите воспользоваться преимуществами хэшей, вы можете использовать hashlib Python для генерации хэшей или использовать что-то вроде pipenv, которое обрабатывает это автоматически (поэзия должна также обрабатывать хеширование, но проверки хешей - это в настоящее время отключен )

Заключение

Зависимость - это еще один вектор атаки, нацеленный на программное обеспечение с открытым исходным кодом, которым все мы любим и от которого зависим. Хотя описанные выше методы должны помочь защитить вас от путаницы с зависимостями, путаница с зависимостями представляет собой относительно узкий вектор атаки. Лучшая защита ваших проектов - это целостный многоуровневый подход. Обязательно ознакомьтесь с бесплатными инструментами SCA, такими как Ochrona, и инструментами SAST, такими как bandit, чтобы встроить дополнительную защиту в процессы разработки.