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) майнер требует минимум взаимодействия с оператором, а доход генерируется автоматически. Кроме того, майнеры часто используют «тёмные» кошельки, что усложняет отслеживание финансовых потоков.

Основные шаги атаки:

  1. Поиск уязвимого сервера (сканирование портов, проверка версии Next.js).
  2. Отправка специально сформированного POST‑запроса, содержащего вредоносный payload.
  3. Скрипт, полученный от сервера, скачивает бинарный файл майнера с удалённого хоста.
  4. Запуск майнера в фоне, подключение к удалённому кошельку.
  5. Автоперезапуск процесса при попытке его завершить (через 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

Возможные решения и рекомендации

  1. Обновление фреймворка. Перейти минимум на Next.js 15, где уязвимость закрыта. При обновлении проверить совместимость с текущим кодом.
  2. Ограничение прав процессов. Запускать Node‑приложения в изолированных контейнерах (Docker, LXC) без привилегий root.
  3. Контроль доступа к сети. Настроить iptables или ufw так, чтобы сервер не мог исходить к неизвестным IP‑адресам, особенно к портам 80/443.
  4. Мониторинг аномальной нагрузки. Использовать инструменты htop, glances или специализированные APM‑системы для быстрого обнаружения всплесков CPU.
  5. Регулярный аудит зависимостей. Периодически запускать сканеры уязвимостей (npm audit, Snyk) и проверять версии пакетов.
  6. Автоматическое откатывание. Настроить CI/CD‑pipeline, который в случае обнаружения подозрительных изменений откатывает репозиторий к последней «чистой» версии.
  7. Логи и алерты. Настроить централизованный сбор логов (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, чтобы каждый коммит проверял безопасность зависимостей.


Оригинал
PREVIOUS ARTICLE