- PVSM.RU - https://www.pvsm.ru -
Эта история началась с обычного git push.
Я работал над проектом, не буду называть его, потому что статья не про конкретную компанию или проект и не про поиск виноватых. Она про то, как вредоносное ПО может попасть в привычный рабочий процесс разработчика, замаскироваться под нормальные Git-изменения и постепенно заражать другие проекты и машины.
Я хочу рассказать это на реальном примере, потому что такие истории обычно кажутся чем-то далеким: «ну это где-то в крупных компаниях», «ну это у тех, кто скачивает странные файлы», «ну я-то опытный разработчик, я замечу».
Как оказалось, заметить можно. Но не сразу. И не всегда спокойно.
В тот день я работал как обычно. К обеду у меня накопились локальные коммиты, и я решил запушить изменения на GitHub.
Но push не прошел. Git сказал, что в удаленной ветке уже есть изменения и сначала нужно сделать pull.
В командной работе это обычная ситуация. Но здесь был важный нюанс: на этом проекте я был практически единственным активным разработчиком. Да, доступ был еще у нескольких людей, но они пушили туда очень редко.
Поэтому сообщение «сначала подтяни изменения» выглядело странно.
Я пошел смотреть, что именно появилось в удаленной ветке.
Когда я открыл историю, меня сильно смутило, что измененным оказался мой же предыдущий коммит.
Не новый коммит от другого человека. Не обычный merge. Не pull request.
А мой коммит, который я делал день или два назад.
Но внутри были файлы, которые я не трогал. Насколько помню, среди них был .gitignore и JavaScript-конфиг, вроде babel. config. js.
Скриншот: в знакомом коммите рядом с обычными изменениями появился подозрительный babel.config.js.
В этом JS-файле были странные обфусцированные строки. То есть код был специально записан так, чтобы человек не мог быстро понять, что он делает.
Скриншот: длинный обфусцированный JavaScript-payload был добавлен прямо в babel.config.js.
Вот в этот момент стало понятно: это не обычная Git-ситуация. Что-то явно пошло не так.
Сначала я решил, что скомпрометирован мой компьютер или мой GitHub.
Честно говоря, состояние в такие моменты неприятное. Ты вроде бы опытный разработчик, понимаешь Git, SSH, токены, доступы, окружения. Но в голове все равно появляется мысль: «Меня взломали, а я даже не понял когда».
Я сообщил коллегам, что, скорее всего, мой аккаунт в GitHub взломан. На тот момент никто из нас еще не понимал масштаба проблемы.
Я начал проверять свой Mac: приложения удаленного доступа, расширения VS Code, плагины RubyMine, системные логи macOS, Docker-логи, development-логи проектов. Искал все, что могло объяснить, как кто-то получил доступ к моему GitHub.
Параллельно я удалял все, что казалось подозрительным: плагины, расширения, приложения. Это была не самая методичная фаза расследования. Скорее стрессовая попытка быстро убрать все потенциально опасное.
Я сообщил руководителю проекта, что ситуация похожа на компрометацию.
Мы решили действовать так, будто утекло все, что могло утечь: SSH-ключи, GitHub-доступ, токены, секреты, пароли, платежные креды.
Мы поменяли SSH-ключи, пароль GitHub, платежные токены и секреты, связанные с сервисами Stripe и PayPal.
Это было логично. Если злоумышленник получил доступ к машине разработчика или GitHub, нельзя исключать, что он мог видеть .env, SSH-ключи, deploy keys, API-токены и другие чувствительные данные.
После этого мне казалось, что самое страшное позади — ведь пару дней был полный штиль.
Но через несколько дней я решил снова проверить проект и последний коммит.
И снова увидел вредоносный код…
Это был очень неприятный момент.
Мы уже поменяли ключи, пароли и секреты. Я уже почистил машину. Но вредоносный скрипт появился снова…в GitHub.
В голове сразу возникла мысль: значит, кто-то все еще имеет доступ. Либо к моей машине, либо к GitHub, либо к какой-то другой части цепочки, которую мы еще не нашли.
Я снова начал смотреть логи: локальные, серверные, Docker, system logs, development logs. Проверял серверы, с которыми работал. Но картина не складывалась.
Если все ключи поменяли, почему атака продолжается?
Ответ оказался в том, что проблема была не только у меня.
В какой-то момент я вспомнил важную деталь.
Как то утром мне приходили email-уведомления от GitHub о пушах. Время тех уведомлений было около 4:58 утра.
Я точно знал, что в это время спал.
Скриншот: GitHub-уведомление о push пришло в 04:58, когда я точно ничего не пушил.
Но самое важное было не только во времени. В уведомлении был подсвечен ник моего товарища в GitHub. То есть снаружи это выглядело так, будто push был связан с его аккаунтом. Именно поэтому я пошел к нему.
Не потому что подозревал его лично, а потому что email-нотификация дала конкретный след: ник, активность, время, push.
Дальше стало еще страннее. Когда я открыл сами коммиты в GitHub, часть изменений выглядела так, будто они подписаны уже моим именем.
Получалась путаница: уведомление вело к одному человеку, Git-история показывала другого, а реальная точка заражения могла быть вообще третьей.
И это важный момент. Такие атаки опасны не только вредоносным кодом, но и тем, что они запутывают следы. Разработчик видит знакомые имена, знакомые коммиты, привычные файлы, и пытается объяснить это как обычную рабочую ситуацию.
Но это была не обычная ситуация.
Я пошел к товарищу и объяснил ситуацию.
Сначала мы не думали, что его компьютер или GitHub тоже скомпрометированы. Скорее проверяли токены, доступы, GitHub Apps, OAuth Apps, SSH-ключи, все возможные способы, через которые мог происходить push.
Но через пару часов стало понятно: похожие симптомы есть и у него.
Потом выяснилось, что похожие признаки есть еще у нескольких разработчиков в других проектах.
Мы начали восстанавливать цепочку по push, коммитам и GitHub-истории. В итоге нашли первого разработчика, у которого заражение появилось раньше всех.
Если у меня первые признаки были примерно 28 марта, то у него похожие изменения появились еще 1 или 2 марта.
Так стало понятно: это не одиночный взлом моего компьютера. Это цепочка заражения.
##«А ты уверен?»
Когда мы вышли на первого разработчика в цепочке, я написал ему, что, судя по истории, именно с его окружения все началось.
Первая реакция была понятной: «А ты уверен?»
Я отправил скриншоты, коммиты, доказательства.
Через какое-то он взял время на перепровекру — и когда вернулся, сказал примерно следующее: «Я помню как видел, что у меня почему-то меняется .gitignore и .env и появляются странные изменения, но не придал этому значения».
И вот это один из главных уроков всей истории.
Если у вас в проекте «сам по себе» меняется .gitignore, package. json, babel. config. js, postcss. config. js, tailwind. config. js, .env или любой другой конфиг, это не мелочь.
Это повод остановиться и разобраться.
Общая схема выглядела примерно так.
Тот самый разработчик на Windows установил что-то вредоносное: возможно, npm-пакет, dev-tool, расширение или инсталлер. После этого в его проектах появился обфусцированный JavaScript-код, который он успешно запушил в GitHub.
Затем мой колега получил задачу развернуть тот самый проект и помочь выполнить ряд задач. Он сделал обычные действия: клонировал репозиторий, установил зависимости через yarn install или похожую команду, запустил и полностью развернул проект.
GIF: пример того, как зараженный проект снаружи может выглядеть как обычное рабочее приложение.
Смотреть MP4-версию в лучшем качестве [2]
Если вредоносный код выполняется на этапе install/build или через dev-tooling, этого может быть достаточно, чтобы скомпрометировать локальное окружение.
Дальше злоумышленники получают доступ к SSH ключам, GitHub-контексту разработчика и начинают заражать уже другие доступные ему репозитории.
Так цепочка идет дальше: один разработчик, второй, третий, несколько проектов, несколько веток.
Это и есть supply chain attack. Заражается не только конечная машина, а сама цепочка разработки и доставки кода.
Позже я начал искать информацию и вышел на материалы про PolinRider.
По данным OpenSourceMalware, PolinRider — это кампания, связанная с компрометацией GitHub-репозиториев через вредоносные npm-пакеты, dev-tooling и обфусцированные JavaScript-payloads.
Иллюстрация из материала OpenSourceMalware о кампании PolinRider.
Исследователи связывают эту активность с DPRK-linked threat actor, то есть с группами, которые ассоциируют с Северной Кореей.
Для обычного разработчика практический вывод не в геополитике. Практический вывод в том, что атака бьет по привычному developer workflow:
установить пакет;
открыть проект;
запустить yarn install;
сделать build;
запушить изменения.
Именно поэтому это настолько опасно. Атака не выглядит как «я скачал вирус». Она выглядит как обычная работа разработчика.
По данным исследователей, PolinRider может добавлять обфусцированный JavaScript в обычные файлы проекта. Например:
postcss.config.mjs
tailwind.config.js
eslint.config.mjs
next.config.mjs
babel.config.js
App.js
app.js
Это хитрый выбор.
Разработчики редко внимательно читают конец config-файла, особенно если проект большой и «вроде работает». А если изменение еще и спрятано в уже существующий коммит, заметить его становится еще сложнее.
В моем случае это тоже выглядело не как отдельный очевидный вирусный файл, а как странные изменения внутри привычного JS-конфига.
Самая неприятная часть — работа с Git-историей.
Исследователи OpenSourceMalware описывают Windows-артефакт temp_auto_push.bat, который берет последний коммит, сохраняет автора, email, сообщение и timestamp, затем делает git commit --amend и force-push.
Скриншот: batch-файлы добавлены в .gitignore, то есть они одновременно используются и прячутся от обычного git status.
На практике это означает: злоумышленник может добавить вредоносный код в последний коммит так, чтобы он выглядел почти как прежний.
Для разработчика это психологически опасно.
Ты открываешь историю и видишь вроде бы знакомый коммит. Автор — ты. Сообщение — твое. Время — похожее. Но внутри уже есть чужие изменения.
Скриншот: коммит выглядит подписанным знакомым автором, но внутри уже огромный подозрительный diff.
Поэтому в таких случаях нельзя доверять только ощущению «коммит вроде мой». Нужно смотреть diff, GitHub events, force-push, измененные файлы, активность по веткам и email-уведомления.
Может показаться, что это проблема только программистов.
Но это не так.
Если вредоносный код попадает в репозиторий, он может дальше попасть:
в CI/CD;
на staging;
на production;
в npm-пакет;
в open-source библиотеку;
в проект клиента;
на сервер, где есть платежные ключи, API-токены, доступы к базам и другим сервисам.
Если у разработчика на машине есть SSH-ключи, GitHub-токены, .env, доступы к облакам, браузерные пароли или криптокошельки, ущерб может выйти далеко за рамки одного репозитория.
Цель подобных атак часто не «сломать сайт». Цель — украсть доступы, секреты, криптовалюту и получить возможность распространяться дальше.
После этой истории я бы очень серьезно относился к таким сигналам:
Git просит pull, хотя вы точно один работали в ветке.
Последний коммит изменился, но вы его не меняли.
В .gitignore появились странные новые строки.
В JS-конфигах появился непонятный обфусцированный код.
В .gitignore появился .bat-файл или другой скрипт, которого вы не добавляли.
В репозитории появились .bat, .cmd, .sh, .woff2 или другие неожиданные файлы.
GitHub присылает уведомления о push в то время, когда вы точно ничего не пушили.
В email-уведомлении подсвечен один GitHub-ник, а коммиты выглядят подписанными другим именем.
После смены паролей и ключей симптомы появляются снова.
Заражены только недавно активные ветки, а старые ветки не тронуты.
Скриншот: появление config.bat в .gitignore — отдельный красный флаг, даже если остальной diff кажется обычным.
Последний пункт тоже интересный. По нашим наблюдениям, злоумышленники трогали не все ветки подряд, а в основном те, где была недавняя активность. Например, если ветка не обновлялась полгода или год, ее могли не трогать. А вот активные ветки заражались.
Я не специалист по incident response, поэтому это не универсальная инструкция. Но по опыту этой истории я бы действовал так:
Остановить обычную работу с проектом.
Не запускать npm install, yarn install, build или dev server в подозрительном репозитории.
Зафиксировать доказательства: скриншоты, commit hash, diff, время, email-уведомления.
Проверить последние коммиты и force-push события.
Проверить все активные ветки, а не только main.
Проверить JS-конфиги, .gitignore, .vscode/tasks.json, подозрительные .bat, .cmd, .sh.
Считать секреты скомпрометированными, если они были доступны на машине или в окружении.
Ротировать GitHub tokens, SSH-ключи, deploy keys, cloud/API/payment secrets.
Проверить GitHub Apps, OAuth Apps, personal access tokens и SSH keys.
Проверить машины всех разработчиков, которые работали с зараженным проектом.
После очистки пересканировать репозитории еще раз через день или два.
Главное — не лечить только один репозиторий. Если заражение шло через машину разработчика или GitHub-контекст, проблема может быть сразу в нескольких проектах.
К статье я хочу приложить два скрипта.
Они не являются антивирусом и не дают стопроцентной гарантии безопасности. Это инструменты первичной диагностики, которые помогают быстро найти явные признаки проблемы.
Я выложил их вместе с примерами в отдельный репозиторий: amir-budaychiev/polinrider-detection-examples [3]. Там есть короткий README с командами для macOS, Ubuntu и Windows, а также скриншоты и материалы из этой статьи.
Первый скрипт нужен для локальной проверки проектов.
Он проходит по локальным репозиториям и ищет:
признаки обфусцированного JavaScript-кода;
подозрительные вставки в JS-конфигах;
наличие .bat-файлов;
случаи, когда .bat-файл указан в .gitignore;
подозрительные изменения в типичных config-файлах.
Это важно, потому что в подобных атаках злоумышленники могут добавлять служебные batch-файлы и одновременно прятать их от Git через .gitignore.
Второй скрипт нужен для проверки логов.
Его можно запускать локально или на сервере. Он помогает искать следы подозрительной активности:
неожиданные команды;
обращения к подозрительным файлам;
следы запуска install/build-скриптов;
обращения к Git;
возможные следы работы вредоносного payload.
Еще раз: если скрипт ничего не нашел, это не значит, что система точно чистая. Но если он что-то нашел, это повод остановиться, сохранить артефакты и провести полноценное расследование.
Эта история неприятна еще и потому, что она бьет по профессиональному самолюбию.
Когда ты давно в разработке, кажется, что базовые вещи ты точно заметишь. Но современные атаки устроены так, чтобы использовать не глупость, а рутину.
Разработчик каждый день устанавливает зависимости. Каждый день переключается между ветками. Каждый день видит конфиги. Каждый день доверяет GitHub, npm, IDE и локальным токенам.
Атака маскируется под эту нормальность.
Именно поэтому защита должна быть не только на уровне «я внимательный», а на уровне процессов:
минимальные доступы;
отдельные SSH-ключи;
GitHub audit logs;
branch protection;
запрет force-push в важных ветках;
review изменений в config-файлах;
периодические сканы;
ротация токенов;
осторожность с npm/yarn install в незнакомых проектах.
Мои главные выводы:
Странный Git-diff — это не мелочь.
.gitignore и JS-конфиги нужно проверять так же внимательно, как бизнес-логику.
Если заражен один разработчик, возможно, заражен уже не один.
Простая смена пароля не помогает, если источник заражения остался активным.
Нельзя доверять только автору коммита: нужно смотреть, кто пушил, когда и что именно изменилось.
Email-уведомления GitHub могут дать важный след.
Supply chain attacks опасны тем, что используют нормальные инструменты разработки.
После инцидента нужно ротировать не только GitHub-пароль, но и все секреты, которые могли быть доступны в окружении.
Эта история началась с обычного git push.
Не с алерта антивируса. Не с падения сервера. Не с письма от GitHub Security.
Просто Git сказал: «сначала сделай pull».
И именно эта маленькая странность помогла заметить большую проблему.
Если вы разработчик, тимлид, владелец небольшого проекта или человек, у которого на компьютере есть доступы к рабочим системам, относитесь к таким сигналам серьезно.
В 2026 году вредоносное ПО может жить не где-то «снаружи», а прямо внутри привычного developer workflow: в npm-пакете, VS Code extension, config-файле, build script или последнем коммите, который выглядит почти как ваш.
Почти…
P.S. - Если статья получит достаточно лайков или комментариев, я выпущу вторую часть — уже с техническим разбором самого обфусцированного скрипта: как он устроен, какие приемы использует и как постепенно раскручивать такой код до понятной логики.
Автор: amir-budaychiev
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/vredonosnoe-po/452361
Ссылки в тексте:
[1] мозг: http://www.braintools.ru
[2] Смотреть MP4-версию в лучшем качестве: https://raw.githubusercontent.com/amir-budaychiev/polinrider-detection-examples/main/screenshots/react_project.mp4
[3] amir-budaychiev/polinrider-detection-examples: https://github.com/amir-budaychiev/polinrider-detection-examples
[4] OpenSourceMalware: PolinRider attack: https://opensourcemalware.com/blog/polinrider-attack
[5] OpenSourceMalware/PolinRider на GitHub: https://github.com/OpenSourceMalware/PolinRider
[6] Wiz Threat Landscape: PolinRider Campaign: https://threats.wiz.io/all-incidents/polinrider-campaign-dprk-linked-supply-chain-attack-infects-github-repositories
[7] Источник: https://habr.com/ru/articles/1040200/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1040200
Нажмите здесь для печати.