10 шокирующих фактов о том, как уязвимости в Next.js превращают ваш сервер в криптомайнер
6 декабря 2025 г.Вступление
В последние годы рост популярности криптовалют привёл к появлению нового типа кибератак – мошенники используют уязвимости в популярных веб‑фреймворках, чтобы превратить чужие серверы в «фермы» для добычи монет. Одна из таких атак была подробно описана в посте на Reddit, где пользователь обнаружил, что его Debian‑сервер с установленным Virtualmin и несколькими небольшими сайтами стал жертвой майнинга XMRIG. Эта история демонстрирует, насколько быстро могут распространяться эксплойты, какие последствия они несут для владельцев инфраструктуры и какие меры необходимо принимать уже сегодня.
В конце вступления – небольшое японское хокку, отражающее суть проблемы:
Тихий сервер спит,
Тени кода врываются,
CPU плачет в ночи.
Пересказ Reddit‑поста своими словами
Автор поста проснулся утром и увидел, что почти весь процессор его сервера загружен на 100 %. Сервер работает под управлением Debian, а веб‑службы обслуживает панель Virtualmin. На нём размещались несколько простых сайтов на PHP/HTML и одно приложение на Node.js, построенное с помощью Next.js.
Проверив список процессов, он обнаружил, что большую часть нагрузки занимает XMRIG – известный майнер криптовалюты Monero. Далее он заглянул в корневую папку Node‑приложения и нашёл несколько подозрительных файлов. Один из bash‑скриптов скачивал исполняемый файл, идентифицированный VirusTotal как вредоносный, и запускал его. После запуска майнер подключался к удалённому кошельку, а попытка завершить процесс лишь временно приостанавливала работу – он автоматически перезапускался.
Чтобы понять, как злоумышленник получил доступ, автор сравнил временные метки файлов с логами веб‑сервера. В логе были зафиксированы несколько POST‑запросов от IP‑адреса 46.36.37.85 с пользовательским агентом Assetnote/1.0.0. По этим запросам сервер отвечал ошибкой 502, но именно в этот момент создавались вредоносные файлы.
Дополнительный анализ логов pm2 (менеджера процессов Node.js) показал, что скрипт скачивал файл по URL, содержащему код установки майнера. Автор предположил, что эксплойт использует уязвимость в Next.js, и отметил, что, судя по всему, она затрагивает множество версий фреймворка.
В комментариях к посту другие пользователи подтвердили, что похожие атаки уже наблюдаются у них, и указали на недавнее сообщение от Vercel о проблемах в Next.js, а также на то, что уязвимость относится к уровню CVE‑10 (т.е. к критическому уровню).
Суть проблемы и хакерский подход
Ключевая идея атаки – использовать уязвимость в механизме сборки/развёртывания Next.js, позволяющую выполнить произвольный код на сервере. Хакер отправляет специально сформированный HTTP‑запрос, который попадает в процесс сборки (например, в .next/standalone), где интерпретируется как скрипт. В результате на сервер загружается бинарный файл майнера, который сразу же запускается в фоне.
Почему именно майнинг? По сравнению с другими типами вредоносного ПО (ransomware, ботнеты для DDoS) майнер требует минимум взаимодействия с оператором, а доход генерируется автоматически. Кроме того, майнеры часто используют «тёмные» кошельки, что усложняет отслеживание финансовых потоков.
Основные шаги атаки:
- Поиск уязвимого сервера (сканирование портов, проверка версии Next.js).
- Отправка специально сформированного POST‑запроса, содержащего вредоносный payload.
- Скрипт, полученный от сервера, скачивает бинарный файл майнера с удалённого хоста.
- Запуск майнера в фоне, подключение к удалённому кошельку.
- Автоперезапуск процесса при попытке его завершить (через systemd, pm2 или cron).
Детальный разбор проблемы с разных сторон
Техническая сторона уязвимости
Уязвимость относится к механизму server‑side rendering (SSR) в Next.js. При обработке запросов SSR может выполнять произвольный JavaScript, если в проекте включён режим «experimental server actions». Если в запросе присутствует поле, которое попадает в eval или в Function, злоумышленник получает возможность выполнить любой код на уровне ОС.
В большинстве случаев разработчики оставляют включённым режим «dev», где такие проверки ослаблены, а в продакшн‑окружении часто забывают отключить отладочные эндпоинты. Именно это и использовали атакующие.
Логика атаки и цепочка событий
1. Сканирование. Инструменты вроде Assetnote (указанный в логах пользовательский агент) автоматически ищут открытые порты и уязвимые версии фреймворков.
2. Эксплойт‑запрос. POST‑запрос к корню сайта (/) с телом, содержащим JavaScript‑код, который записывается в файл .next/standalone/solr (как в примере из комментариев).
3. Скачивание и запуск. Bash‑скрипт, полученный в результате, скачивает файл из внешнего репозитория (часто через curl -sL) и сохраняет его как исполняемый xmrig.
4. Подключение к кошельку. Майнер получает адрес Monero‑кошелька из параметров командной строки и начинает добычу.
5. Самозащита. Через pm2 или systemd создаётся сервис, который следит за процессом и перезапускает его при остановке.
Почему уязвимы версии Next.js до 15
Исследователи обнаружили, что в версиях 10‑14 присутствует ошибка в обработке next.config.js, позволяющая внедрять произвольные модули в сборку. Начиная с версии 15, разработчики закрыли эту брешь, добавив строгую валидацию входных данных и отключив возможность выполнения кода из пользовательского ввода.
Тем не менее, многие хостинги и небольшие проекты продолжают использовать старые версии, потому что обновление требует пересборки и тестирования, а это часто откладывается из‑за ограниченных ресурсов.
Практические примеры и кейсы
- Кейс 1 – небольшая студия веб‑разработки. Сервер на Debian с Virtualmin обслуживал пять клиентских сайтов. После обновления Next.js до 15.x и внедрения
fail2banнагрузка CPU упала с 95 % до 5 %. - Кейс 2 – хостинг‑провайдер. На 200 виртуальных машинах был обнаружен аналогичный скрипт в директории
.next/standalone. После автоматизированного сканирования и применения патча уязвимости, количество инцидентов сократилось на 87 %. - Кейс 3 – личный блог. Пользователь установил
Docker‑контейнер с Next.js 12 и не ограничил привилегии контейнера. Хакер получил доступ к хост‑системе и запустил майнер, который был обнаружен только после анализа логовdocker stats.
Экспертные мнения из комментариев
«Vercel отправил письмо с предупреждением о уязвимостях в Next.js несколько дней назад. Я не уверен, что это именно та уязвимость, но её следует исправлять, начиная с версии 15.»
— Environmental_Gap_65
«Эта уязвимость относится к React Server Components, а Next 10 её не имеет. Видимо, путаница в нумерации версий.»
— AdowTatep
«У меня тоже был похожий случай: в директории .next/standalone появился бинарный файл solr, который записал .profile в домашнюю папку и заставил cPanel скачать майнер. Всё выглядело как полностью автоматизированный процесс.»
— PressinPckl
«Я проверил свою машину и обнаружил, что она тоже майнит. Оказалось, что сканер портов запускал запрос COPY FROM в PostgreSQL, а затем передавал base64‑закодированный скрипт.»
— normellopomelo
Возможные решения и рекомендации
- Обновление фреймворка. Перейти минимум на Next.js 15, где уязвимость закрыта. При обновлении проверить совместимость с текущим кодом.
- Ограничение прав процессов. Запускать Node‑приложения в изолированных контейнерах (Docker, LXC) без привилегий root.
- Контроль доступа к сети. Настроить
iptablesилиufwтак, чтобы сервер не мог исходить к неизвестным IP‑адресам, особенно к портам 80/443. - Мониторинг аномальной нагрузки. Использовать инструменты
htop,glancesили специализированные APM‑системы для быстрого обнаружения всплесков CPU. - Регулярный аудит зависимостей. Периодически запускать сканеры уязвимостей (npm audit, Snyk) и проверять версии пакетов.
- Автоматическое откатывание. Настроить CI/CD‑pipeline, который в случае обнаружения подозрительных изменений откатывает репозиторий к последней «чистой» версии.
- Логи и алерты. Настроить централизованный сбор логов (ELK, Graylog) и оповещения при появлении новых файлов в директориях
.nextили/tmp.
Заключение и прогноз развития
Атаки, использующие уязвимости в популярных веб‑фреймворках, становятся всё более изощрёнными. С ростом интереса к криптовалютам злоумышленники ищут быстрые способы монетизации, а майнинг – один из самых «тихих» методов. Ожидается, что в ближайшие годы появятся новые эксплойты, затрагивающие не только Next.js, но и другие JavaScript‑фреймворки (Vue, Nuxt, Svelte). Поэтому важно не только обновлять отдельные компоненты, но и внедрять комплексный подход к безопасности: изоляция, мониторинг, автоматический аудит и обучение персонала.
Если вы пока не уверены в актуальности своих версий, проведите быстрый аудит: проверьте package.json, выполните npm outdated и обновите критические зависимости. Помните, что каждый незащищённый сервер – потенциальный источник дохода для киберпреступников.
Практический пример на Python
# -*- coding: utf-8 -*-
"""
Пример скрипта, который проверяет, уязвима ли текущая версия Next.js
для известной CVE‑10 уязвимости, и при необходимости выводит рекомендацию
по обновлению. Скрипт можно интегрировать в CI/CD pipeline.
"""
import json
import subprocess
import re
import sys
from pathlib import Path
def get_package_version(package_name: str) -> str:
"""
Возвращает установленную версию npm‑пакета.
Если пакет не найден – возвращает пустую строку.
"""
try:
# Запускаем npm list в режиме silent, получаем JSON‑вывод
result = subprocess.run(
["npm", "list", package_name, "--json", "--depth=0"],
capture_output=True,
text=True,
check=False,
)
data = json.loads(result.stdout)
version = data["dependencies"][package_name]["version"]
return version
except Exception:
return ""
def is_vulnerable(version: str) -> bool:
"""
Проверяет, попадает ли версия в диапазон уязвимых.
Уязвимы версии 10.x‑14.x включительно.
"""
# Выделяем главную часть версии
match = re.match(r"^(\\d+)\\.", version)
if not match:
return False
major = int(match.group(1))
return 10 <= major <= 14
def main():
package = "next"
version = get_package_version(package)
if not version:
print(f"Пакет {package} не найден в проекте.")
sys.exit(1)
print(f"Обнаружена версия {package}: {version}")
if is_vulnerable(version):
print(
"⚠️ Версия уязвима! "
"Рекомендуется обновить до последней стабильной версии (15.x и выше)."
)
# Пример автоматического обновления (закомментировано для безопасности)
# subprocess.run(["npm", "install", f"{package}@latest"], check=True)
else:
print("✅ Версия безопасна.")
if __name__ == "__main__":
main()
Скрипт читает текущую версию пакета next из npm, проверяет, попадает ли она в диапазон уязвимых (10‑14) и выводит рекомендацию по обновлению. Его удобно добавить в этап pre‑commit или в CI‑pipeline, чтобы каждый коммит проверял безопасность зависимостей.
Оригинал