Статья vm2 sandbox escape: разбор трёх критических побегов CVSS 10.0 в Node.js

Крупный план платы с вскрытым экранированием и перебитым шлейфом. Выжженный чип с гравировкой кода уязвимости под лупой на чёрном антистатическом коврике.


Тринадцать CVE с оценкой от 9.0 до 10.0 за первую половину 2026 года - в одной библиотеке, которую мейнтейнер пометил deprecated ещё в 2023-м. По данным The Hacker News, каждая ведёт к выполнению произвольного кода на хост-системе. Три из них - CVE-2026-43997, CVE-2026-44005 и CVE-2026-44006 - набрали максимальный балл 10.0 и показывают три фундаментально разных примитива побега из песочницы: утечку host Object, prototype pollution через bridge proxy и инъекцию через BaseHandler.getPrototypeOf. Разбираю каждый по отдельности и показываю, почему proxy-модель изоляции vm2 - это не стена, а занавеска.

Бизнес-логика атаки: зачем ломать песочницу vm2​

vm2 - npm-пакет для запуска недоверенного JavaScript в изолированной среде Node.js. По данным Endor Labs, библиотека набирает больше миллиона еженедельных загрузок, хотя мейнтейнер ещё в 2023 году пометил проект как deprecated: "contains critical security issues and should not be used for production""Содержит критически важные элементы безопасности и не должен использоваться в производственных целях". Но кого это когда останавливало? vm2 продолжают тащить в IDE, FaaS-платформы, пентест-фреймворки и - что сейчас особенно больно - в фреймворки AI-агентов для выполнения LLM-сгенерированного кода. Подробнее - в нашем руководстве по cve эксплойт разработка.

Побег из песочницы Node.js превращает выполнение JavaScript внутри vm2 в полноценный RCE на хост-системе. Для AI-агентов, как отмечает Kodem Security, цепочка выглядит так: prompt injection -> вредоносный JS в sandbox -> vm2 sandbox escape -> shell на хосте. CISA через программу ADP-Vulnrichment классифицирует все три CVE как automatable с total technical impact (SSVC Decision: Track*). PoC-код существует для каждой из трёх.

Место в цепочке атаки: Initial Access (T1190, Exploit Public-Facing Application) -> Execution (T1059.007, JavaScript) -> Privilege Escalation (T1611, Escape to Host). Код попадает в vm2 через пользовательский ввод, LLM-генерацию или плагинную систему - дальше хватает одного из трёх описанных ниже примитивов для полной компрометации хоста.

Модель изоляции vm2: proxy-обёртки и их пределы​

1783887127072.webp

Требования к окружению для воспроизведения​

  • Node.js: v18+ (для воспроизведения CVE-2026-43997, CVE-2026-44005, CVE-2026-44006)
  • npm-пакет vm2: версии до 3.10.5 включительно (все три CVE исправлены в 3.11.0)
  • ОС: Linux, macOS или Windows - vm2 платформенно-независима
  • RAM: стандартные требования Node.js (от 256 МБ)
  • Контекст: только лабораторная среда без доступа к продакшн-данным
vm2 строит изоляцию на двух механизмах, целиком живущих на уровне JavaScript. Первый - трансформатор кода: он инжектит handleException() в catch-клаузы, чтобы перехватывать host-ошибки. Второй - bridge proxy: каждый объект, пересекающий границу sandbox <-> host, оборачивается в Proxy, который перехватывает обращения к свойствам и методам. По замыслу sandbox-код никогда не получает прямую ссылку на host-объект - только на его проксированную копию.

Загвоздка в том, что оба механизма работают целиком в userland JavaScript. Никаких V8 Isolates (отдельных куч памяти), никаких OS-примитивов изоляции (namespaces, seccomp). Любая ошибка в логике проксирования, обработке исключений или обходе прототипной цепочки даёт атакующему прямую ссылку на host-объект. А от host-объекта до Function constructor - ровно два шага по цепочке obj.constructor.constructor.

vm2 закрывала такие ошибки точечными патчами начиная с 2022 года: CVE-2022-36067 (CVSS 10.0, EPSS 0.4787 - Top 5%), CVE-2023-29017 (CVSS 10.0, EPSS 0.6319 - Top 1%), CVE-2023-30547 (CVSS 9.8, EPSS 0.7209 - Top 1%). Каждый раз фикс закрывал один конкретный вектор - и каждый раз находился следующий. Майская волна 2026 года - продолжение этого бесконечного цикла.

CVE-2026-43997: утечка host Object через Symbol​

1783887158325.webp

CVSS: 10.0 (CRITICAL) - AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
CWE: CWE-94 (Code Injection), CWE-653 (Improper Isolation or Compartmentalization)
Затронутые версии: npm vm2 до 3.11.0 (по данным OSV.dev: introduced 0, fixed 3.11.0)
EPSS: 0.0098 (percentile 57.94%)

Суть: sandbox-код получает прямую ссылку на host Object - не на проксированную копию, а на настоящий Object из host-контекста. По описанию NVD, один из способов эксплуатации - вызов HostObject.getOwnPropertySymbols() для получения Symbol(nodejs.util.inspect.custom).

Почему это плохо: Symbol(nodejs.util.inspect.custom) - внутренний символ Node.js для кастомной сериализации объектов. Если ты видишь его из sandbox - значит, работаешь с реальным host-объектом, а не с proxy-обёрткой. Дальше стандартная цепочка: host Object -> Object.constructor -> Function -> произвольный код в host-контексте.
JavaScript:
// Пример для демонстрации концепции - CVE-2026-43997
// Атакующий внутри sandbox получает host Object
const symbols = Object.getOwnPropertySymbols(hostObject);
const inspectSym = symbols.find(
  s => String(s).includes('nodejs.util.inspect.custom')
);
// Наличие этого символа подтверждает работу с host-объектом
// Далее: hostObject.constructor.constructor('return process')()
CWE-653 (Improper Isolation) - корневая причина: механизм изоляции vm2 не предотвращает утечку host Object в sandbox-контекст. CWE-94 (Code Injection) - следствие: атакующий конструирует произвольный код через Function constructor. Исследователь c0rydoras, которому The Hacker News приписывает обнаружение этой CVE, продемонстрировал полную цепочку от получения host Object до RCE.

Предусловия и ограничения: нужна возможность выполнить произвольный JavaScript внутри vm2 sandbox (класс VM или NodeVM). Если приложение фильтрует входной код до передачи в vm2 (AST-анализ, whitelist-функций), вектор может быть заблокирован до достижения уязвимого пути. На практике большинство деплоев vm2 не применяют pre-sandbox фильтрацию - в этом и смысл использования песочницы. Зачем городить AST-парсер, если у тебя якобы sandbox?

CVE-2026-44005: prototype pollution через bridge proxy​

1783887228742.webp

CVSS: 10.0 (CRITICAL) - AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H
CWE: CWE-94 (Code Injection), CWE-1321 (Improper Control of Modification of Object Prototype Attributes Prototype Pollution), CWE-653 (Improper Isolation)
Затронутые версии: npm vm2 от 3.9.6 до 3.10.5 (по данным OSV.dev)
EPSS: 0.0084 (percentile 53.60%)

Эта уязвимость отличается от остальных побегов - атакующий не получает прямой RCE через Function constructor, а мутирует host-прототипы прямо из sandbox-кода. Обратите внимание на CVSS-вектор: C:N/I:H/A:H - конфиденциальность не затронута напрямую, зато целостность и доступность - полностью.

По описанию NVD: vm2-bridge экспонирует мутабельные proxy для реальных host-прототипов (Object.prototype, Array.prototype, Function.prototype) и проксирует записи из sandbox в host через otherReflectSet() и otherReflectDefineProperty(). Sandbox-код пишет в Object.prototype - и эта мутация проходит сквозь bridge в реальный host-контекст.
JavaScript:
// Пример для демонстрации концепции - CVE-2026-44005
// Внутри sandbox: запись в Object.prototype
Object.prototype.__polluted__ = true;
// vm2 bridge вызывает otherReflectSet() на host Object.prototype
// В host-контексте: ({})['__polluted__'] === true
// Весь Node.js-процесс скомпрометирован - middleware, auth-проверки
CWE-1321 (Prototype Pollution) - точное описание механики. Тут принципиально другой подход: атакующему не нужен child_process.exec() напрямую. JavaScript sandbox bypass через prototype pollution в host-контексте бьёт по всему Node.js-процессу - другие модули, middleware, проверки авторизации. В комбинации с CVE-2026-43997 или CVE-2026-44006 получается полная цепочка: сначала prototype pollution для подготовки среды, затем прямой побег для RCE. Красиво, если смотреть со стороны атакующего.

Ограничения: эксплуатация требует версии vm2 начиная с 3.9.6 (до неё bridge-механизм работал иначе). Если приложение запускает vm2 в отдельном Worker Thread с --frozen-intrinsics, мутация прототипов может быть частично ограничена - но я не встречал такой конфигурации в проде.

CVE-2026-44006: произвольные прототипы через getPrototypeOf​

1783887259760.webp

CVSS: 10.0 (CRITICAL) - AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
CWE: CWE-94 (Code Injection), CWE-914 (Improper Control of Dynamically-Identified Variables)
Затронутые версии: npm vm2 до 3.11.0 (по данным OSV.dev: introduced 0, fixed 3.11.0)
EPSS: 0.0081 (percentile 52.77%)

Третий примитив атакует механизм проксирования в лоб. По описанию NVD: sandbox-код добирается до метода BaseHandler.getPrototypeOf, который возвращает произвольные прототипы из host-контекста.

BaseHandler - внутренний обработчик vm2, управляющий Proxy-ловушками для всех объектов, пересекающих границу sandbox <-> host. Если атакующий добирается до getPrototypeOf этого обработчика, он получает прямой доступ к прототипной цепочке host-объектов - без проксирования и санитизации. Обход изоляции JavaScript здесь опирается на сами механизмы прототипного наследования языка. Ирония в том, что vm2 пытается контролировать именно те механизмы, которые используются для её обхода.

CWE-914 (Improper Control of Dynamically-Identified Variables) - корневая проблема: vm2 не контролирует доступ к внутренним переменным (BaseHandler), которые идентифицируются динамически через прототипную цепочку. Атакующий использует механизмы самого JavaScript (prototype chain traversal) для доступа к внутренним компонентам изоляции.

Исследователь c0rydoras обнаружил и эту CVE (по данным The Hacker News) - что указывает на системный аудит proxy-модели vm2, а не случайные находки. Когда один человек вытаскивает три CVSS 10.0 из одной библиотеки - это не везение, это методичный разбор архитектуры.

Классификация уязвимостей: CWE, CWE-913, CWE-74 и MITRE ATT&CK​

Иерархия CWE для тройки CVE​

Три CVE объединены корневой слабостью CWE-94 (Code Injection), но каждая добавляет свой специфичный CWE:

CVECVSSСпецифичный CWEПримитив побега
CVE-2026-4399710.0CWE-653 (Improper Isolation)Host Object leak
CVE-2026-4400510.0CWE-1321 (Prototype Pollution)Bridge proxy mutation
CVE-2026-4400610.0CWE-914 (Dynamic Variable Control)getPrototypeOf injection

В иерархии CWE все три попадают под два родительских класса, которые точно описывают природу vm2-побегов:

CWE-913 (Improper Control of Dynamically-Managed Code Resources) - CWE-94 дочерний для CWE-913 (связь ChildOf). Библиотека не контролирует динамически управляемые ресурсы кода - прототипы, конструкторы, символы - к которым sandbox-код получает доступ. CWE-913 явно присвоен родственной CVE-2026-22709 (CVSS 9.8) и историческим CVE-2022-36067, CVE-2023-29017, CVE-2023-29199. Одна и та же корневая слабость, разные пути эксплуатации.

CWE-74 (Improper Neutralization of Special Elements in Output Used by a Downstream Component) - второй родительский класс CWE-94. Атакующий формирует "специальные элементы" (обращения к прототипам, символам, внутренним обработчикам), которые vm2 не нейтрализует при пересечении границы sandbox -> host. CWE-74 явно присвоен CVE-2023-30547 (CVSS 9.8, EPSS 0.7209 - Top 1%), подтверждая общий паттерн.

Маппинг на MITRE ATT&CK и EPSS-прогноз​

Цепочка атаки через побег из песочницы Node.js:
  1. Initial Access - T1190 (Exploit Public-Facing Application): код попадает в vm2 через веб-интерфейс, API или LLM-генерацию
  2. Execution - T1059.007 (JavaScript): вредоносный JS выполняется внутри vm2
  3. Privilege Escalation - T1611 (Escape to Host): один из трёх примитивов пробивает границу sandbox
  4. Post-Exploitation - T1106 (Native API): доступ к child_process, fs, net через host-контекст
EPSS на июль 2026: CVE-2026-43997 - 0.0098 (percentile 57.94%), CVE-2026-44005 - 0.0084 (percentile 53.60%), CVE-2026-44006 - 0.0081 (percentile 52.77%). Пока показатели умеренные. Для сравнения: CVE-2023-30547 с аналогичной механикой sandbox escape набрал EPSS 0.7209 (Top 1%), а CVE-2023-29017 - 0.6319 (Top 1%). Обоим потребовалось время для появления в публичных exploit kits. Текущая траектория указывает на рост EPSS по мере распространения PoC-кода - и я бы не стал ждать.

Обнаружение и митигация vm2 sandbox escape​

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
(V8 Isolates с отдельными кучами памяти), Docker/gVisor контейнеры, Firecracker microVM. Каждая из них торгует производительность и удобство API на уровень изоляции. Если приложение выполняет LLM-сгенерированный код - контейнерная изоляция минимум, не vm2.

Вся майская волна 2026 года - не аномалия, а закономерный результат архитектурного решения строить sandbox на JavaScript-уровне без аппаратной или OS-уровневой изоляции. За время работы с vm2 breakouts наблюдаю одну и ту же последовательность: исследователь находит новый путь к host-объекту -> мейнтейнер закрывает конкретный путь -> через недели появляется следующий. Бесконечный whack-a-mole.

CVE-2026-43997, CVE-2026-44005 и CVE-2026-44006 показывают три принципиально разных примитива (host Object leak, prototype pollution, getPrototypeOf injection) - и все три обходят ту же proxy-модель разными дорогами. Это не баги. Это свойство архитектуры.

Единственный честный вопрос для команд, которые используют vm2 в проде: не "какую версию поставить", а "почему всё ещё vm2, а не V8 Isolate или контейнерная изоляция". Prototype pollution через bridge proxy (CVE-2026-44005) - особенно показательный случай: атакующий даже не пытается выбраться из sandbox классическим путём, он мутирует host-прототипы изнутри и ломает весь Node.js-процесс. Следующая волна CVE будет - вопрос когда, не "если". EPSS для исторических vm2-побегов стабильно растёт до Top 1% в течение года после раскрытия, и у свежей тройки нет архитектурных причин отклониться от этой траектории. Запустите npm ls vm2 в своих проектах прямо сейчас - если увидите версию ниже 3.11.2, у вас та же проблема.
 
Последнее редактирование модератором:
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🧭 Навигатор · ИБ 2026
Не знаешь, какой трек твой?
5 направлений ИБ, реальные зарплаты и точка входа для каждого — в одном треде.
JuniorSenior+
100K → 600K+ ₽ /мес
Открыть навигатор →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab