Уязвимость в скриптах сборки Python пакетов создает риск компрометации домена

information security

Исследователи кибербезопасности из ReversingLabs обнаружили опасную уязвимость в legacy-пакетах Python, которая потенциально позволяет злоумышленникам провести атаку на репозиторий PyPI (Python Package Index) через компрометацию домена. Хотя уязвимый код в основном не используется в современных средах разработки, он может сохраняться в унаследованных производственных системах.

Описание

Машинная обученная модель RL Spectra Assure Community, обнаруживающая пакеты с поведением, аналогичным известному вредоносному ПО, выявила проблему в bootstrap-файлах инструмента сборки, который устанавливает пакет distribute и выполняет другие задачи в процессе начальной загрузки. Эти скрипты автоматизируют процесс загрузки, сборки и установки необходимых библиотек и инструментов. Конкретно, при выполнении bootstrap-скрипта он загружает и выполняет скрипт установки пакета distribute с домена python-distribute[.]org - устаревшего домена, который сейчас доступен для продажи по премиальной цене и управляется для получения дохода от рекламы.

Под угрозой находятся Python-пакеты, включающие bootstrap-скрипт, обращающийся к освобожденному домену, среди которых tornado (асинхронная сетевая библиотека), pypiserver (для настройки частных серверов, подобных PyPI), slapos.core (операционная система для распределенных POSIX-инфраструктур), roman (для преобразования числительных), xlutils (редактирование Excel-файлов), testfixtures (модульные тесты и документация) и как минимум дюжина других популярных пакетов на PyPI.

Исторический контекст уязвимости

В ранние дни упаковки Python пакет distutils предназначался для этих целей и включался в стандартную библиотеку вплоть до Python 3.12, где был удален после устаревания в Python 3.10. Библиотека setuptools была разработана для заполнения отсутствующих функций, таких как автоматическая загрузка и установка зависимостей пакетов. Пакет предоставлял утилиту командной строки easy_install, которая именно это и делала. В какой-то момент distribute ответвился от setuptools из-за воспринимаемого медленного развития сообществом, но был объединен обратно несколькими годами позже в 2013 году.

Ситуация была позже исправлена Python Packaging Authority (PyPA), причем packaging стал ключевым проектом, а setuptools - де-факто отраслевым стандартом. Однако до этого разработчикам и поставщикам инструментов сборки приложений приходилось работать с каждым подходом, получившим достаточное распространение в сообществе открытого исходного кода. При этом они заложили основу для потенциальных компрометаций цепочки поставок через атаку захвата домена.

Проблема bootstrap.py

Среди всех доступных вариантов упаковки появился другой инструмент: zc.buildout, который представлял собой инструмент автоматизации, использовавшийся в 2010-х годах для построения сложных приложений с высокой гранулярностью при обеспечении воспроизводимости. Инструмент zc.buildout предоставлял файл bootstrap.py своим пользователям для упрощения установки пакета buildout - позволяя пользователям фиксировать версии buildout в .cfg-файлах. С намерением поддерживать как пользователей setuptools, так и distribute, он оставил дверь открытой для атаки захвата домена - возможность, доступная злоумышленникам с 2014 года, которая, по-видимому, не была использована до сегодняшнего дня.

Bootstrap-скрипты отличаются друг от друга тем, используют ли они distribute по умолчанию или только при предоставлении соответствующего флага. В любом случае пользователь выполнял бы bootstrap-скрипт. Альтернативно, скрипт мог вызываться Makefile - стандартным текстовым файлом, используемым утилитой make, которая автоматизирует сборку программного обеспечения. Если скрипту было указано использовать distribute, он загружал и выполнял его скрипт установки. Скрипт установки distribute загружается с python-distribute[.]org, домена, который выставлен на продажу с 2014 года.

Хотя разрешение проблемы со стороны PyPI подтолкнуло разработчиков отказаться от пакета distribute (в основном это произошло), миграция была добровольной, а не обязательной, и поэтому не была выполнена тщательно. Многие пакеты продолжали поставлять bootstrap-скрипт, который устанавливал distribute либо по умолчанию, либо когда пользователи предоставляли соответствующий флаг командной строки.

Сегодня distribute фигурально мертв - объединен обратно в setuptools с опубликованным слоем совместимости, который просто устанавливает версию setuptools, опубликованную после слияния distribute. Но bootstrap-скрипты все еще пытаются установить distribute, открывая двери для выполнения произвольного кода, размещенного на заброшенном домене - таким образом подвергая разработчиков, запускающих bootstrap-скрипт, риску загрузки и выполнения вредоносного кода.

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

Доказательство концепции

Чтобы продемонстрировать, как это может произойти, исследователи RL создали простой скрипт доказательства концепции, эксплуатирующий уязвимость. Команда выбрала использование Python 2 при создании этого примера, потому что сам bootstrap-скрипт написан на Python 2 и не может быть выполнен с Python 3 без модификаций. Код использует bootstrap-скрипт, поставляемый с пакетом slapos.core.

Более подробный взгляд на то, как домен python-distribute[.]org управлялся с тех пор, как PyPA взяла на себя задачу наведения порядка в практиках упаковки Python, показал, что форк distribute от setuptools был объединен обратно в setuptools в июне 2013 года. Записи истории DNS проливают свет на то, как это повлияло на управление доменом. Записи показывают, что домен был отброшен в октябре 2013 года, что было замечено пользователями zc-buildout, только чтобы быть созданным снова два дня спустя, потому что он был отброшен по ошибке. Затем он удерживался еще год, снова отброшен в октябре 2014 года и припаркован для получения дохода от рекламы с декабря 2014 года. Это могло быть легко использовано злоумышленником, потому что, поскольку он был окончательно отброшен, любой мог купить домен и обслуживать любой вредоносный код, который он хотел, в качестве своего bootstrap-скрипта.

Прецеденты и выводы

Подобная атака не была бы новой. В 2023 году популярный пакет npm fsevents был скомпрометирован злоумышленником, который взял под контроль незаявленный облачный ресурс. Эта проблема захвата домена была замечена в 2023 году и затронула версии пакета от 1.0.0 до 1.2.11, что отражено в CVE-2023-45311. Версии в указанном диапазоне загружают исполняемый файл с жестко заданного URL-адреса, указывающего на ресурс облачного хранилища. Ресурс был заявлен threat adversary, доставляя вредоносные исполняемые файлы пользователям, устанавливающим пакет.

Пример fsevents показывает, как захваты доменов уже эксплуатировались в дикой природе, и насколько важно быть проактивным в выявлении таких рисков. RL Spectra Assure Community помогает обнаруживать уязвимость как в fsevents, так и в пакетах, затронутых включением bootstrap-скрипта, загружающего и выполняющего контент с домена python-distribute[.]org.

Проблема заключается в шаблоне программирования, который включает загрузку и выполнение полезной нагрузки (payload) с жестко заданного домена, что является шаблоном, часто наблюдаемым в вредоносном ПО, проявляющем поведение загрузчика. Сообщество Python созрело и преодолело многие трудности, связанные с упаковкой, включая переход от distribute к более надежным и тщательно управляемым альтернативам, таким как setuptools. Однако неспособность официально вывести из эксплуатации модуль distribute позволила уязвимым bootstrap-скриптам сохраняться и оставила неизвестное количество проектов подверженными потенциальной атаке.

Урок для сообщества открытого исходного кода ясен: зависимость от жестко заданных доменов сопряжена с повышенными рисками компрометации цепочки поставок. Следовательно, такие домены не должны освобождаться из-под контроля доверенных владельцев без предварительного обеспечения миграции пользователей от доступа к ним. Это также служит напоминанием о более крупной проблеме старения кода, которая преследует сообщество открытого исходного кода, и о том, насколько важно обеспечивать, чтобы ваши продукты не полагались на устаревшие и небезопасные практики кодирования. В случае домена python-distribute[.]org не задокументировано злоупотребление этой уязвимостью, но того же нельзя сказать о пакете fsevents на npm.

Индикаторы компрометации

SHA1

  • 0cbb7df358b8772f4d5f2d346fe87bf6f5b911c1
  • 357f2fe2684c54339fb78ff447d8cbc127071633
  • 3cdd1cfcc254a382338588e406493924d4aadb4f
  • 3d4970cbd4540e4bf1dc67ca554228b6369629d8
  • 76eccfddbec55f435145e2620826ba30e22e5653
  • 8285c1c8188f198e9440c97e8aed933704b32d82
  • 9f02932adcbafc7f4c681df66b597df10f34b134
  • b6664ae860ca4793e368abb0c569ddefcbc7ac96
  • cc298ff510dee97bf5f8abac68bafc22bcfadc11
  • df52f054feb6f2e0df2db1a22c5781d3c56e8ffa
  • e68c89e553a2e70380492bdb8cfb74c224456766

PyPI packages

Package name Version range
pypiserver >=1.1.1, < 2.4.0
slapos.core >=0, <=1.19.0
roman >=2.0.0, <3.2
xlutils >=1.6.0, <2.0.0
testfixtures >=2.3.4, <3.0.2
imio-pm-locales >=4.1.18.1, <4.2.20
pyquery >= 1.2.10, <2.0.0
Комментарии: 0