Статья CVE-2026-44643: Angular Expressions sandbox escape — RCE через вредоносные фильтры

Руки в перчатках держат вскрытую плату над лотком, рядом монитор с искажённым текстом об уязвимости и распечатка фрагмента package-lock.json под криминалистической линейкой.


Четыре CVE за пять лет в одном npm-пакете, и каждый раз - побег из sandbox с выходом на произвольное выполнение кода на сервере. На пентесте Node.js-бэкенда SaaS-платформы для генерации договоров нашёл в package-lock.json зависимость angular-expressions@1.4.3 - версия с патчем для CVE-2024-54152, но дырявая для CVE-2026-44643 (патч - 1.5.2). Пакет затянулся транзитивно через docxtemplater. Пользователи загружали Word-шаблоны с placeholder-выражениями, движок компилировал их на сервере без санитизации. Один вредоносный фильтр в выражении - и вместо подстановки {user.name | uppercase} сервер отдаёт содержимое process.env с credentials от PostgreSQL и S3.

angular-expressions: серверный движок с клиентской ДНК​

Путаница между angular-expressions и AngularJS (фреймворк 1.x в браузере) - одна из причин, по которой уязвимость долго живёт незамеченной в dependency tree. angular-expressions - standalone npm-модуль от peerigon, который вытащил движок выражений из AngularJS 1.x и адаптировал для серверного использования в Node.js. Это не AngularJS-фреймворк (клиентский MVC, давно EOL) и не Angular 2+ (другая кодовая база). Здесь речь о библиотеке, которая парсит и исполняет выражения вида user.name | uppercase на сервере, внутри Node.js-процесса.

Главный потребитель - docxtemplater, одна из самых популярных библиотек для генерации Word-документов из шаблонов. Встречается и в PDF-генераторах, CMS и внутренних инструментах, где нужна динамическая подстановка данных.

Для защиты от выполнения произвольного кода модуль реализует sandbox - блокирует доступ к опасным объектам: window, process, eval, Function, цепочки [B]proto[/B] и constructor. Проблема в том, что sandbox унаследовал архитектуру из AngularJS 1.x, где его официально признали несостоятельным и удалили в версии 1.6. Как показывает исследование PortSwigger, sandbox AngularJS ломали многократно - от Mario Heiderich в самом начале до серии обходов каждой версии вплоть до 1.5.11.

История побегов из sandbox angular-expressions​

CVE-2026-44643 - четвёртый задокументированный побег из sandbox в этом пакете:

CVEGHSAГодВектор
CVE-2020-5219GHSA-hxhm-96pp-2m432020RCE через sandbox escape (патч: >= 1.0.1, CWE-74)
CVE-2021-21277GHSA-j6px-jwvv-vpwq2021RCE, отдельный путь обхода (патч: >= 1.1.2, CWE-74/CWE-94)
CVE-2024-54152GHSA-5462-4vcx-jh7j2024RCE через sandbox escape (патч: >= 1.4.3)
CVE-2026-44643-2026RCE через фильтры

Четыре RCE за пять лет - это не баги, это паттерн. Каждый раз исследователи находят новый путь мимо sandbox (детали раскрыты в GitHub Security Advisories, публичные PoC в Exploit-DB отсутствуют), а мейнтейнеры фиксят конкретный обход, не пересматривая модель. По классификации OWASP уязвимость попадает одновременно под A03:2021 - Injection (CWE-95, eval injection через выражения) и A06:2021 - Vulnerable and Outdated Components (модуль с рецидивирующими критическими уязвимостями).

Анатомия CVE-2026-44643: побег через вредоносные фильтры (CWE-95)​

Согласно описанию в NVD, уязвимость позволяет атакующему сформировать выражение с использованием фильтров, которое обходит sandbox и выполняет произвольный код. Затронуты все версии angular-expressions до 1.5.2 (пакет peerigon/angular-expressions). Классификация - CWE-95 (Improper Neutralization of Directives in Dynamically Evaluated Code, Eval Injection): продукт получает ввод от пользователя, но не нейтрализует синтаксис кода перед использованием в динамическом вызове eval-типа. CWE-95 - подтип CWE-94 (Code Injection), специфичен для языков с динамической оценкой выражений: JavaScript, Python, Perl, PHP. Все они относятся к одному семейству Injection/Code Injection.

Фильтры в angular-expressions работают через оператор |: выражение value | filterName:arg передаёт результат левой части в функцию-фильтр. Внутри парсера фильтры обрабатываются по отдельному пути кода - и именно тут часть ссылок разрешается иначе, чем в основном выражении. Атакующий эксплуатирует эту разницу, чтобы:
  1. Через фильтровый путь получить доступ к запрещённой ссылке, которая в основном выражении была бы заблокирована sandbox
  2. Восстановить доступ к Function через цепочку прототипов: любой объект → .constructor (конструктор объекта, например String) → .constructor (конструктор конструктора - Function)
  3. Вызвать Function('return process')() - создать анонимную функцию, возвращающую объект process Node.js
  4. Исполнить произвольный код в контексте Node.js - уже за пределами sandbox
Концептуальная иллюстрация паттерна эксплуатации (рабочий PoC не опубликован в Exploit-DB; схема основана на описании из advisory):
JavaScript:
const { compile } = require('angular-expressions');
const scope = { user: { name: 'test' } };
const expr = compile(
  "constructor.constructor('return process')() | someFilter // someFilter должен быть зарегистрирован; точный payload не раскрыт в CVE-записи"
);
const result = expr(scope);
// result → объект process Node.js
// → доступ к process.env, require('child_process') и далее
Точный payload зависит от конкретного пути, найденного исследователями - детали не раскрыты в CVE-записи. Но паттерн constructor.constructor для обхода sandbox AngularJS описан в исследованиях PortSwigger начиная с 2016 года (в контексте клиентского AngularJS, без публичных PoC для серверного angular-expressions в Exploit-DB). CVE-2026-41468 (Beghelli Sicuro24, CWE-1104) документирует использование того же примитива constructor.constructor в клиентском AngularJS 1.5.2 (embedded EOL-компонент), но в браузерном контексте с MITM-вектором (AV:A, UI:P). В серверном angular-expressions последствия серьёзнее: вместо XSS через MITM - полный RCE на Node.js.

Разбор CVSS 4.0 вектора​

CVSS 9.3 (CRITICAL) по NVD. Ключевые компоненты вектора:

КомпонентЗначениеИнтерпретация
AV:NNetworkЭксплуатация удалённо, через сеть
AC:LLowНизкая сложность атаки
AT:NNoneНет дополнительных предусловий
PR:NNoneПривилегии не требуются
UI:NNoneДействие пользователя не требуется
VC:H / VI:H / VA:HHighПолный импакт на конфиденциальность, целостность и доступность уязвимого компонента
SC:N / SI:N / SA:NNoneФормально нет воздействия за пределами уязвимого компонента

SC/SI/SA:N - формально скоуп не меняется, импакт ограничен уязвимым компонентом. На практике скомпрометированный Node.js-процесс почти всегда хранит в process.env credentials для баз данных, очередей и облачных сервисов, что делает pivot на смежные системы тривиальным. Формальный скоуп CVSS этого не учитывает - поэтому в векторе CVSS 4.0 по NVD 9.3, а cveo.tech публикует 10.0 с SC:H. Расхождение показательное.

По оценке CISA (SSVC): Exploitation = none (подтверждённых случаев эксплуатации нет), Automatable = yes (эксплуатация поддаётся автоматизации), Technical Impact = total. EPSS-оценка: 0.0048, перцентиль 0.3808 - вероятность эксплуатации в 30-дневном окне ниже медианы. Но автоматизируемость (Automatable: yes) означает, что появление публичного PoC мгновенно сдвинет этот показатель.

Цепочка эксплуатации SSTI: от fingerprinting до удалённого выполнения кода​

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

. Если сервис принимает пользовательские шаблоны (а это его основная функция), атакующий вставляет в DOCX выражение с sandbox escape вместо легитимного placeholder-а. Генератор договоров превращается в точку входа.

Шаг 1 - Fingerprinting. На внешнем пентесте ищу признаки использования docxtemplater: эндпоинты /api/generate, /api/template, /api/document, наличие DOCX в ответах или в описании API. Поисковые строки в JavaScript-бандлах: docxtemplater, angular-expressions, angularParser. На внутреннем (grey box) - запрашиваю package-lock.json или выполняю npm ls angular-expressions на сервере. Версия ниже 1.5.2 - вектор есть.

Шаг 2 - Поиск точки входа. Нужно найти место, где пользовательский ввод попадает в compile(). Типичные кандидаты: загрузка DOCX-шаблона, параметры API для кастомизации документа, формы с динамической генерацией контента. В Burp Suite перехватываю запросы к эндпоинтам генерации и модифицирую содержимое шаблона.

Шаг 3 - Верификация без деструктивных действий. Canary-выражение: подставляю в шаблон {1+1} и проверяю, возвращается ли 2 в сгенерированном документе. Если да - выражения компилируются и исполняются. Дальше - {user.name.constructor}. Если sandbox работает, выражение вернёт ошибку или пустое значение. Если в ответе function String() { [native code] } - sandbox не блокирует доступ к цепочке прототипов. Можно двигаться дальше.

Шаг 4 - Контролируемая эксплуатация. При подтверждённом sandbox escape получаю содержимое process.env - наименее деструктивная проверка, подтверждающая RCE. Фиксирую для отчёта: payload, скриншот ответа с переменными окружения (замаскировав значения), CVSS-вектор, рекомендации.

Ограничения Angular sandbox bypass: когда техника не сработает​

Работает если:
  • angular-expressions версии < 1.5.2 (прямая или транзитивная зависимость)
  • Пользовательский ввод достигает вызова compile()expr(scope)
  • Нет WAF/application-level фильтрации (regex-санитизация обходима, см. оговорку ниже)
Не работает если:
  • Версия angular-expressions >= 1.5.2 - патч закрывает фильтровый вектор
  • Выражения захардкожены в коде и пользовательский ввод в них не попадает. Даже при уязвимой версии без контролируемого ввода в compile() эксплуатация невозможна
  • Между вводом и compile() стоит санитизация, блокирующая constructor, prototype, [B]proto[/B], Function(. Оговорка: regex-санитизация - пластырь. Как отмечает cveo.tech, история sandbox escape в AngularJS доказывает, что креативные payload-ы обходят наивные regex через Unicode-обфускацию, String.fromCharCode, конкатенацию токенов
  • Приложение использует Angular 2+ - у него нет angular-expressions и нет этого sandbox
  • Node.js-процесс запущен в изолированном контейнере без сетевого доступа и с read-only FS - RCE формально достигается, но post-exploitation сильно ограничен
Контекст пентеста. На внешнем пентесте без доступа к исходникам fingerprinting зависимости сложнее - опираюсь на поведенческие индикаторы (реакция на выражения в шаблонах). На внутреннем/grey box - прямая проверка через npm ls, результат за минуту.

Детектирование инъекции шаблонов и митигация (CWE-95)​

Аудит зависимостей​

Первый шаг - выяснить, есть ли angular-expressions в dependency tree. Команда npm ls angular-expressions 2>&1 | grep -v "deduped" покажет все вхождения, включая транзитивные. Точная загруженная версия: node -e "console.log(require('angular-expressions/package.json').version)". Для yarn и pnpm - yarn why angular-expressions и pnpm why angular-expressions соответственно.

Обновление: npm install angular-expressions@^1.5.2. Если зависимость транзитивная (через docxtemplater), используйте overrides в package.json:
JSON:
{
  "overrides": {
    "angular-expressions": "^1.5.2"
  }
}
Свежие релизы docxtemplater бандлят angular-expressions >= 1.5.2 - проверяйте и обновляйте.

Мониторинг и сетевые IOC​

Если приложение логирует вычисляемые выражения (а если нет - включите), ищите паттерны sandbox escape:
Bash:
# Поиск артефактов sandbox escape в логах приложения
grep -E "constructor|prototype|__proto__|Function\(|globalThis" \
  /var/log/app/*.log
# Аудит зависимости
npm ls angular-expressions 2>&1 | grep -v "deduped"
На уровне runtime-защиты добавьте правило, алертящее на вычисление выражений с constructor.constructor, [B]proto[/B], Function(, globalThis. Сетевые IOC после успешной эксплуатации: аномальный исходящий трафик от Node.js-процессов, DNS-запросы к неизвестным доменам, подключения к адресам вне allow-листа, скачки CPU (криптомайнинг).

Чеклист для пентестера и сисадмина​

  1. Проверить наличие angular-expressions в dependency tree командой npm ls angular-expressions
  2. Определить точную версию - если < 1.5.2, уязвимость подтверждена
  3. Обновить до angular-expressions@^1.5.2 напрямую или через overrides
  4. Проверить, попадает ли пользовательский ввод в compile() - без этого условия эксплуатация невозможна даже при уязвимой версии
  5. Если патчинг задерживается - добавить regex-санитизацию как временную меру: блокировать паттерны constructor, prototype, [B]proto[/B], Function(, eval, globalThis, process, require. Помнить, что это НЕ замена патча
  6. Включить логирование вычисляемых выражений, настроить мониторинг на паттерны escape
  7. Проверить сетевую изоляцию Node.js-сервера: ограничить исходящие подключения, настроить allow-лист
  8. Для долгосрочного hardening: не использовать angular-expressions для обработки пользовательского ввода. Для user-supplied шаблонов - isolated-vm, отдельный worker thread или контейнер с ограниченными привилегиями
Четвёртый побег из sandbox в одном пакете за пять лет - это архитектурный приговор. Sandbox в angular-expressions унаследован от AngularJS 1.x, где разработчики сами признали его несостоятельность и удалили. Но пакет продолжает жить на npm и затягивается в проекты через docxtemplater - зачастую без ведома разработчиков, которые даже не подозревают, что их генератор договоров тянет за собой движок выражений с четырьмя RCE в послужном списке.

Паттерн повторяется: команда фиксит конкретный путь обхода, но не пересматривает модель. Sandbox, построенный на блокировке конкретных свойств JavaScript-объектов, обречён - язык слишком динамичен, цепочки прототипов слишком гибки, а парсер выражений слишком сложен для исчерпывающего контроля. Через год-два будет пятый побег. Не потому что мейнтейнеры некомпетентны, а потому что задача «безопасно исполнять произвольные выражения пользователя в том же процессе Node.js» принципиально нерешаема через allow/deny-листы свойств. Единственная надёжная изоляция - процессная или контейнерная, с отдельным рантаймом, без доступа к process, require и сетевому стеку. Всё остальное - вопрос времени до следующего CVE.
 
Мы в соцсетях:

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

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

HackerLab